# HG changeset patch # User Alain Mazy # Date 1663258354 -7200 # Node ID cd9521e042493e49cb780aee44e6106fcfd6d2d4 # Parent 7671fa7f099ee81a1b6996a71f50bc6a5ce46c77 DatabaseBackendAdapterV4: added support for customData + revision when not already done diff -r 7671fa7f099e -r cd9521e04249 .hgignore --- a/.hgignore Tue Jul 05 08:44:26 2022 +0200 +++ b/.hgignore Thu Sep 15 18:12:34 2022 +0200 @@ -3,5 +3,6 @@ PostgreSQL/ThirdPartyDownloads/ Odbc/ThirdPartyDownloads/ MySQL/ThirdPartyDownloads/ +SQLite/ThirdPartyDownloads/ .vscode/ diff -r 7671fa7f099e -r cd9521e04249 Framework/Plugins/DatabaseBackendAdapterV2.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV2.cpp Tue Jul 05 08:44:26 2022 +0200 +++ b/Framework/Plugins/DatabaseBackendAdapterV2.cpp Thu Sep 15 18:12:34 2022 +0200 @@ -184,7 +184,8 @@ const std::string& uncompressedHash, int32_t compressionType, uint64_t compressedSize, - const std::string& compressedHash) ORTHANC_OVERRIDE + const std::string& compressedHash, + const std::string& /*customData*/) ORTHANC_OVERRIDE { OrthancPluginAttachment attachment; attachment.uuid = uuid.c_str(); @@ -216,7 +217,8 @@ const std::string& uncompressedHash, int32_t compressionType, uint64_t compressedSize, - const std::string& compressedHash) ORTHANC_OVERRIDE + const std::string& compressedHash, + const std::string& /*customData*/) ORTHANC_OVERRIDE { if (allowedAnswers_ != AllowedAnswers_All && allowedAnswers_ != AllowedAnswers_Attachment) diff -r 7671fa7f099e -r cd9521e04249 Framework/Plugins/DatabaseBackendAdapterV3.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV3.cpp Tue Jul 05 08:44:26 2022 +0200 +++ b/Framework/Plugins/DatabaseBackendAdapterV3.cpp Thu Sep 15 18:12:34 2022 +0200 @@ -584,7 +584,8 @@ const std::string& uncompressedHash, int32_t compressionType, uint64_t compressedSize, - const std::string& compressedHash) ORTHANC_OVERRIDE + const std::string& compressedHash, + const std::string& /*customData*/) ORTHANC_OVERRIDE { OrthancPluginDatabaseEvent event; event.type = OrthancPluginDatabaseEventType_DeletedAttachment; @@ -630,7 +631,8 @@ const std::string& uncompressedHash, int32_t compressionType, uint64_t compressedSize, - const std::string& compressedHash) ORTHANC_OVERRIDE + const std::string& compressedHash, + const std::string& /*customData*/) ORTHANC_OVERRIDE { SetupAnswerType(_OrthancPluginDatabaseAnswerType_Attachment); diff -r 7671fa7f099e -r cd9521e04249 Framework/Plugins/DatabaseBackendAdapterV4.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp Thu Sep 15 18:12:34 2022 +0200 @@ -0,0 +1,2117 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2021 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "DatabaseBackendAdapterV4.h" + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + +#include +#include +#include + +#include +#include +#include +#include + + +#define ORTHANC_PLUGINS_DATABASE_CATCH(context) \ + catch (::Orthanc::OrthancException& e) \ + { \ + return static_cast(e.GetErrorCode()); \ + } \ + catch (::std::runtime_error& e) \ + { \ + const std::string message = "Exception in database back-end: " + std::string(e.what()); \ + OrthancPluginLogError(context, message.c_str()); \ + return OrthancPluginErrorCode_DatabasePlugin; \ + } \ + catch (...) \ + { \ + OrthancPluginLogError(context, "Native exception"); \ + return OrthancPluginErrorCode_DatabasePlugin; \ + } + + +namespace OrthancDatabases +{ + static bool isBackendInUse_ = false; // Only for sanity checks + + + template + static void CopyListToVector(std::vector& target, + const std::list& source) + { + /** + * This has the the same effect as: + * + * target.reserve(source.size()); + * std::copy(std::begin(source), std::end(source), std::back_inserter(target)); + * + * However, this implementation is compatible with C++03 (Linux + * Standard Base), whereas "std::back_inserter" requires C++11. + **/ + + target.clear(); + target.reserve(source.size()); + + for (typename std::list::const_iterator it = source.begin(); it != source.end(); ++it) + { + target.push_back(*it); + } + } + + + class DatabaseBackendAdapterV4::Adapter : public boost::noncopyable + { + private: + class ManagerReference : public Orthanc::IDynamicObject + { + private: + DatabaseManager* manager_; + + public: + ManagerReference(DatabaseManager& manager) : + manager_(&manager) + { + } + + DatabaseManager& GetManager() + { + assert(manager_ != NULL); + return *manager_; + } + }; + + std::unique_ptr backend_; + OrthancPluginContext* context_; + boost::shared_mutex connectionsMutex_; + size_t countConnections_; + std::list connections_; + Orthanc::SharedMessageQueue availableConnections_; + + public: + Adapter(IndexBackend* backend, + size_t countConnections) : + backend_(backend), + countConnections_(countConnections) + { + if (countConnections == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "There must be a non-zero number of connections to the database"); + } + else if (backend == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + context_ = backend_->GetContext(); + } + } + + ~Adapter() + { + for (std::list::iterator + it = connections_.begin(); it != connections_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + } + + OrthancPluginContext* GetContext() const + { + return context_; + } + + void OpenConnections() + { + boost::unique_lock lock(connectionsMutex_); + + if (connections_.size() == 0) + { + assert(backend_.get() != NULL); + + { + std::unique_ptr manager(new DatabaseManager(backend_->CreateDatabaseFactory())); + manager->GetDatabase(); // Make sure to open the database connection + + backend_->ConfigureDatabase(*manager); + connections_.push_back(manager.release()); + } + + for (size_t i = 1; i < countConnections_; i++) + { + connections_.push_back(new DatabaseManager(backend_->CreateDatabaseFactory())); + connections_.back()->GetDatabase(); // Make sure to open the database connection + } + + for (std::list::iterator + it = connections_.begin(); it != connections_.end(); ++it) + { + assert(*it != NULL); + availableConnections_.Enqueue(new ManagerReference(**it)); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + void CloseConnections() + { + boost::unique_lock lock(connectionsMutex_); + + if (connections_.size() != countConnections_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (availableConnections_.GetSize() != countConnections_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Some connections are still in use, bug in the Orthanc core"); + } + else + { + for (std::list::iterator + it = connections_.begin(); it != connections_.end(); ++it) + { + assert(*it != NULL); + (*it)->Close(); + } + } + } + + class DatabaseAccessor : public boost::noncopyable + { + private: + boost::shared_lock lock_; + Adapter& adapter_; + DatabaseManager* manager_; + + public: + DatabaseAccessor(Adapter& adapter) : + lock_(adapter.connectionsMutex_), + adapter_(adapter), + manager_(NULL) + { + for (;;) + { + std::unique_ptr manager(adapter.availableConnections_.Dequeue(100)); + if (manager.get() != NULL) + { + manager_ = &dynamic_cast(*manager).GetManager(); + return; + } + } + } + + ~DatabaseAccessor() + { + assert(manager_ != NULL); + adapter_.availableConnections_.Enqueue(new ManagerReference(*manager_)); + } + + IndexBackend& GetBackend() const + { + return *adapter_.backend_; + } + + DatabaseManager& GetManager() const + { + assert(manager_ != NULL); + return *manager_; + } + }; + }; + + + class DatabaseBackendAdapterV4::Output : public IDatabaseBackendOutput + { + private: + struct Metadata + { + int32_t metadata; + const char* value; + }; + + _OrthancPluginDatabaseAnswerType answerType_; + std::list stringsStore_; + + std::vector attachments_; + std::vector changes_; + std::vector tags_; + std::vector exported_; + std::vector events_; + std::vector integers32_; + std::vector integers64_; + std::vector matches_; + std::vector metadata_; + std::vector stringAnswers_; + + const char* StoreString(const std::string& s) + { + stringsStore_.push_back(s); + return stringsStore_.back().c_str(); + } + + void SetupAnswerType(_OrthancPluginDatabaseAnswerType type) + { + if (answerType_ == _OrthancPluginDatabaseAnswerType_None) + { + answerType_ = type; + } + else if (answerType_ != type) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + public: + Output() : + answerType_(_OrthancPluginDatabaseAnswerType_None) + { + } + + void Clear() + { + // We don't systematically clear all the vectors, in order to + // avoid spending unnecessary time + + switch (answerType_) + { + case _OrthancPluginDatabaseAnswerType_None: + break; + + case _OrthancPluginDatabaseAnswerType_Attachment: + attachments_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_Change: + changes_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_DicomTag: + tags_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_ExportedResource: + exported_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_Int32: + integers32_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_Int64: + integers64_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_MatchingResource: + matches_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_Metadata: + metadata_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_String: + stringAnswers_.clear(); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + answerType_ = _OrthancPluginDatabaseAnswerType_None; + stringsStore_.clear(); + events_.clear(); + + assert(attachments_.empty()); + assert(changes_.empty()); + assert(tags_.empty()); + assert(exported_.empty()); + assert(events_.empty()); + assert(integers32_.empty()); + assert(integers64_.empty()); + assert(matches_.empty()); + assert(metadata_.empty()); + assert(stringAnswers_.empty()); + } + + + OrthancPluginErrorCode ReadAnswersCount(uint32_t& target) const + { + switch (answerType_) + { + case _OrthancPluginDatabaseAnswerType_None: + target = static_cast(0); + break; + + case _OrthancPluginDatabaseAnswerType_Attachment: + target = static_cast(attachments_.size()); + break; + + case _OrthancPluginDatabaseAnswerType_Change: + target = static_cast(changes_.size()); + break; + + case _OrthancPluginDatabaseAnswerType_DicomTag: + target = static_cast(tags_.size()); + break; + + case _OrthancPluginDatabaseAnswerType_ExportedResource: + target = static_cast(exported_.size()); + break; + + case _OrthancPluginDatabaseAnswerType_Int32: + target = static_cast(integers32_.size()); + break; + + case _OrthancPluginDatabaseAnswerType_Int64: + target = static_cast(integers64_.size()); + break; + + case _OrthancPluginDatabaseAnswerType_MatchingResource: + target = static_cast(matches_.size()); + break; + + case _OrthancPluginDatabaseAnswerType_Metadata: + target = static_cast(metadata_.size()); + break; + + case _OrthancPluginDatabaseAnswerType_String: + target = static_cast(stringAnswers_.size()); + break; + + default: + return OrthancPluginErrorCode_InternalError; + } + + return OrthancPluginErrorCode_Success; + } + + + OrthancPluginErrorCode ReadAnswerAttachment2(OrthancPluginAttachment2& target /* out */, + uint32_t index) const + { + if (index < attachments_.size()) + { + target = attachments_[index]; + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + OrthancPluginErrorCode ReadAnswerChange(OrthancPluginChange& target /* out */, + uint32_t index) const + { + if (index < changes_.size()) + { + target = changes_[index]; + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + OrthancPluginErrorCode ReadAnswerDicomTag(uint16_t& group, + uint16_t& element, + const char*& value, + uint32_t index) const + { + if (index < tags_.size()) + { + const OrthancPluginDicomTag& tag = tags_[index]; + group = tag.group; + element = tag.element; + value = tag.value; + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + OrthancPluginErrorCode ReadAnswerExportedResource(OrthancPluginExportedResource& target /* out */, + uint32_t index) const + { + if (index < exported_.size()) + { + target = exported_[index]; + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + OrthancPluginErrorCode ReadAnswerInt32(int32_t& target, + uint32_t index) const + { + if (index < integers32_.size()) + { + target = integers32_[index]; + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + OrthancPluginErrorCode ReadAnswerInt64(int64_t& target, + uint32_t index) const + { + if (index < integers64_.size()) + { + target = integers64_[index]; + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + OrthancPluginErrorCode ReadAnswerMatchingResource(OrthancPluginMatchingResource& target, + uint32_t index) const + { + if (index < matches_.size()) + { + target = matches_[index]; + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + OrthancPluginErrorCode ReadAnswerMetadata(int32_t& metadata, + const char*& value, + uint32_t index) const + { + if (index < metadata_.size()) + { + const Metadata& tmp = metadata_[index]; + metadata = tmp.metadata; + value = tmp.value; + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + OrthancPluginErrorCode ReadAnswerString(const char*& target, + uint32_t index) const + { + if (index < stringAnswers_.size()) + { + target = stringAnswers_[index].c_str(); + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + OrthancPluginErrorCode ReadEventsCount(uint32_t& target /* out */) const + { + target = static_cast(events_.size()); + return OrthancPluginErrorCode_Success; + } + + + OrthancPluginErrorCode ReadEvent2(OrthancPluginDatabaseEvent2& event /* out */, + uint32_t index) const + { + if (index < events_.size()) + { + event = events_[index]; + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_ParameterOutOfRange; + } + } + + + virtual void SignalDeletedAttachment(const std::string& uuid, + int32_t contentType, + uint64_t uncompressedSize, + const std::string& uncompressedHash, + int32_t compressionType, + uint64_t compressedSize, + const std::string& compressedHash, + const std::string& customData) ORTHANC_OVERRIDE + { + OrthancPluginDatabaseEvent2 event; + event.type = OrthancPluginDatabaseEventType_DeletedAttachment; + event.content.attachment.uuid = StoreString(uuid); + event.content.attachment.contentType = contentType; + event.content.attachment.uncompressedSize = uncompressedSize; + event.content.attachment.uncompressedHash = StoreString(uncompressedHash); + event.content.attachment.compressionType = compressionType; + event.content.attachment.compressedSize = compressedSize; + event.content.attachment.compressedHash = StoreString(compressedHash); + event.content.attachment.customData = StoreString(customData); + + events_.push_back(event); + } + + + virtual void SignalDeletedResource(const std::string& publicId, + OrthancPluginResourceType resourceType) ORTHANC_OVERRIDE + { + OrthancPluginDatabaseEvent2 event; + event.type = OrthancPluginDatabaseEventType_DeletedResource; + event.content.resource.level = resourceType; + event.content.resource.publicId = StoreString(publicId); + + events_.push_back(event); + } + + + virtual void SignalRemainingAncestor(const std::string& ancestorId, + OrthancPluginResourceType ancestorType) ORTHANC_OVERRIDE + { + OrthancPluginDatabaseEvent2 event; + event.type = OrthancPluginDatabaseEventType_RemainingAncestor; + event.content.resource.level = ancestorType; + event.content.resource.publicId = StoreString(ancestorId); + + events_.push_back(event); + } + + + virtual void AnswerAttachment(const std::string& uuid, + int32_t contentType, + uint64_t uncompressedSize, + const std::string& uncompressedHash, + int32_t compressionType, + uint64_t compressedSize, + const std::string& compressedHash, + const std::string& customData) ORTHANC_OVERRIDE + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_Attachment); + + OrthancPluginAttachment2 attachment; + attachment.uuid = StoreString(uuid); + attachment.contentType = contentType; + attachment.uncompressedSize = uncompressedSize; + attachment.uncompressedHash = StoreString(uncompressedHash); + attachment.compressionType = compressionType; + attachment.compressedSize = compressedSize; + attachment.compressedHash = StoreString(compressedHash); + attachment.customData = StoreString(customData); + + attachments_.push_back(attachment); + } + + + virtual void AnswerChange(int64_t seq, + int32_t changeType, + OrthancPluginResourceType resourceType, + const std::string& publicId, + const std::string& date) ORTHANC_OVERRIDE + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_Change); + + OrthancPluginChange change; + change.seq = seq; + change.changeType = changeType; + change.resourceType = resourceType; + change.publicId = StoreString(publicId); + change.date = StoreString(date); + + changes_.push_back(change); + } + + + virtual void AnswerDicomTag(uint16_t group, + uint16_t element, + const std::string& value) ORTHANC_OVERRIDE + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_DicomTag); + + OrthancPluginDicomTag tag; + tag.group = group; + tag.element = element; + tag.value = StoreString(value); + + tags_.push_back(tag); + } + + + virtual void AnswerExportedResource(int64_t seq, + OrthancPluginResourceType resourceType, + const std::string& publicId, + const std::string& modality, + const std::string& date, + const std::string& patientId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid) ORTHANC_OVERRIDE + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_ExportedResource); + + OrthancPluginExportedResource exported; + exported.seq = seq; + exported.resourceType = resourceType; + exported.publicId = StoreString(publicId); + exported.modality = StoreString(modality); + exported.date = StoreString(date); + exported.patientId = StoreString(patientId); + exported.studyInstanceUid = StoreString(studyInstanceUid); + exported.seriesInstanceUid = StoreString(seriesInstanceUid); + exported.sopInstanceUid = StoreString(sopInstanceUid); + + exported_.push_back(exported); + } + + + virtual void AnswerMatchingResource(const std::string& resourceId) ORTHANC_OVERRIDE + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_MatchingResource); + + OrthancPluginMatchingResource match; + match.resourceId = StoreString(resourceId); + match.someInstanceId = NULL; + + matches_.push_back(match); + } + + + virtual void AnswerMatchingResource(const std::string& resourceId, + const std::string& someInstanceId) ORTHANC_OVERRIDE + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_MatchingResource); + + OrthancPluginMatchingResource match; + match.resourceId = StoreString(resourceId); + match.someInstanceId = StoreString(someInstanceId); + + matches_.push_back(match); + } + + + void AnswerIntegers32(const std::list& values) + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_Int32); + CopyListToVector(integers32_, values); + } + + + void AnswerIntegers64(const std::list& values) + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_Int64); + CopyListToVector(integers64_, values); + } + + + void AnswerInteger64(int64_t value) + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_Int64); + + integers64_.resize(1); + integers64_[0] = value; + } + + + void AnswerMetadata(int32_t metadata, + const std::string& value) + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_Metadata); + + Metadata tmp; + tmp.metadata = metadata; + tmp.value = StoreString(value); + + metadata_.push_back(tmp); + } + + + void AnswerStrings(const std::list& values) + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_String); + CopyListToVector(stringAnswers_, values); + } + + + void AnswerString(const std::string& value) + { + SetupAnswerType(_OrthancPluginDatabaseAnswerType_String); + + if (stringAnswers_.empty()) + { + stringAnswers_.push_back(value); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + }; + + + IDatabaseBackendOutput* DatabaseBackendAdapterV4::Factory::CreateOutput() + { + return new DatabaseBackendAdapterV4::Output; + } + + + class DatabaseBackendAdapterV4::Transaction : public boost::noncopyable + { + private: + Adapter& adapter_; + std::unique_ptr accessor_; + std::unique_ptr output_; + + public: + Transaction(Adapter& adapter) : + adapter_(adapter), + accessor_(new Adapter::DatabaseAccessor(adapter)), + output_(new Output) + { + } + + ~Transaction() + { + } + + IndexBackend& GetBackend() const + { + return accessor_->GetBackend(); + } + + Output& GetOutput() const + { + return *output_; + } + + DatabaseManager& GetManager() const + { + return accessor_->GetManager(); + } + }; + + + static OrthancPluginErrorCode ReadAnswersCount(OrthancPluginDatabaseTransaction* transaction, + uint32_t* target /* out */) + { + assert(target != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswersCount(*target); + } + + + static OrthancPluginErrorCode ReadAnswerAttachment2(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginAttachment2* target /* out */, + uint32_t index) + { + assert(target != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswerAttachment2(*target, index); + } + + + static OrthancPluginErrorCode ReadAnswerChange(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginChange* target /* out */, + uint32_t index) + { + assert(target != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswerChange(*target, index); + } + + + static OrthancPluginErrorCode ReadAnswerDicomTag(OrthancPluginDatabaseTransaction* transaction, + uint16_t* group, + uint16_t* element, + const char** value, + uint32_t index) + { + assert(group != NULL); + assert(element != NULL); + assert(value != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswerDicomTag(*group, *element, *value, index); + } + + + static OrthancPluginErrorCode ReadAnswerExportedResource(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginExportedResource* target /* out */, + uint32_t index) + { + assert(target != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswerExportedResource(*target, index); + } + + + static OrthancPluginErrorCode ReadAnswerInt32(OrthancPluginDatabaseTransaction* transaction, + int32_t* target, + uint32_t index) + { + assert(target != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswerInt32(*target, index); + } + + + static OrthancPluginErrorCode ReadAnswerInt64(OrthancPluginDatabaseTransaction* transaction, + int64_t* target, + uint32_t index) + { + assert(target != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswerInt64(*target, index); + } + + + static OrthancPluginErrorCode ReadAnswerMatchingResource(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginMatchingResource* target, + uint32_t index) + { + assert(target != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswerMatchingResource(*target, index); + } + + + static OrthancPluginErrorCode ReadAnswerMetadata(OrthancPluginDatabaseTransaction* transaction, + int32_t* metadata, + const char** value, + uint32_t index) + { + assert(metadata != NULL); + assert(value != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswerMetadata(*metadata, *value, index); + } + + + static OrthancPluginErrorCode ReadAnswerString(OrthancPluginDatabaseTransaction* transaction, + const char** target, + uint32_t index) + { + assert(target != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadAnswerString(*target, index); + } + + + static OrthancPluginErrorCode ReadEventsCount(OrthancPluginDatabaseTransaction* transaction, + uint32_t* target /* out */) + { + assert(target != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadEventsCount(*target); + } + + + static OrthancPluginErrorCode ReadEvent2(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginDatabaseEvent2* event /* out */, + uint32_t index) + { + assert(event != NULL); + const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); + return that.GetOutput().ReadEvent2(*event, index); + } + + + static OrthancPluginErrorCode Open(void* database) + { + DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); + + try + { + adapter->OpenConnections(); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + } + + + static OrthancPluginErrorCode Close(void* database) + { + DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); + + try + { + adapter->CloseConnections(); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + } + + + static OrthancPluginErrorCode DestructDatabase(void* database) + { + DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); + + if (adapter == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + else + { + if (isBackendInUse_) + { + isBackendInUse_ = false; + } + else + { + OrthancPluginLogError(adapter->GetContext(), "More than one index backend was registered, internal error"); + } + + delete adapter; + + return OrthancPluginErrorCode_Success; + } + } + + + static OrthancPluginErrorCode GetDatabaseVersion(void* database, + uint32_t* version) + { + DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); + + try + { + DatabaseBackendAdapterV4::Adapter::DatabaseAccessor accessor(*adapter); + *version = accessor.GetBackend().GetDatabaseVersion(accessor.GetManager()); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + } + + + static OrthancPluginErrorCode UpgradeDatabase(void* database, + OrthancPluginStorageArea* storageArea, + uint32_t targetVersion) + { + DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); + + try + { + DatabaseBackendAdapterV4::Adapter::DatabaseAccessor accessor(*adapter); + accessor.GetBackend().UpgradeDatabase(accessor.GetManager(), targetVersion, storageArea); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + } + + + static OrthancPluginErrorCode HasRevisionsSupport(void* database, + uint8_t* target) + { + DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); + + try + { + DatabaseBackendAdapterV4::Adapter::DatabaseAccessor accessor(*adapter); + *target = (accessor.GetBackend().HasRevisionsSupport() ? 1 : 0); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + } + + + static OrthancPluginErrorCode HasAttachmentCustomDataSupport(void* database, + uint8_t* target) + { + DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); + + try + { + DatabaseBackendAdapterV4::Adapter::DatabaseAccessor accessor(*adapter); + *target = (accessor.GetBackend().HasAttachmentCustomDataSupport() ? 1 : 0); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + } + + static OrthancPluginErrorCode StartTransaction(void* database, + OrthancPluginDatabaseTransaction** target /* out */, + OrthancPluginDatabaseTransactionType type) + { + DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); + + try + { + std::unique_ptr transaction(new DatabaseBackendAdapterV4::Transaction(*adapter)); + + switch (type) + { + case OrthancPluginDatabaseTransactionType_ReadOnly: + transaction->GetManager().StartTransaction(TransactionType_ReadOnly); + break; + + case OrthancPluginDatabaseTransactionType_ReadWrite: + transaction->GetManager().StartTransaction(TransactionType_ReadWrite); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + *target = reinterpret_cast(transaction.release()); + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + } + + + static OrthancPluginErrorCode DestructTransaction(OrthancPluginDatabaseTransaction* transaction) + { + if (transaction == NULL) + { + return OrthancPluginErrorCode_NullPointer; + } + else + { + delete reinterpret_cast(transaction); + return OrthancPluginErrorCode_Success; + } + } + + + static OrthancPluginErrorCode Rollback(OrthancPluginDatabaseTransaction* transaction) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetManager().RollbackTransaction(); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode Commit(OrthancPluginDatabaseTransaction* transaction, + int64_t fileSizeDelta /* TODO - not used? */) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetManager().CommitTransaction(); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode AddAttachment2(OrthancPluginDatabaseTransaction* transaction, + int64_t id, + const OrthancPluginAttachment2* attachment, + int64_t revision) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().AddAttachment2(t->GetManager(), id, *attachment, revision); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode ClearChanges(OrthancPluginDatabaseTransaction* transaction) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().ClearChanges(t->GetManager()); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode ClearExportedResources(OrthancPluginDatabaseTransaction* transaction) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().ClearExportedResources(t->GetManager()); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode ClearMainDicomTags(OrthancPluginDatabaseTransaction* transaction, + int64_t resourceId) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().ClearMainDicomTags(t->GetManager(), resourceId); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode CreateInstance(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginCreateInstanceResult* target /* out */, + const char* hashPatient, + const char* hashStudy, + const char* hashSeries, + const char* hashInstance) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + if (t->GetBackend().HasCreateInstance()) + { + t->GetBackend().CreateInstance(*target, t->GetManager(), hashPatient, hashStudy, hashSeries, hashInstance); + } + else + { + t->GetBackend().CreateInstanceGeneric(*target, t->GetManager(), hashPatient, hashStudy, hashSeries, hashInstance); + } + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode DeleteAttachment(OrthancPluginDatabaseTransaction* transaction, + int64_t id, + int32_t contentType) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().DeleteAttachment(t->GetOutput(), t->GetManager(), id, contentType); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode DeleteMetadata(OrthancPluginDatabaseTransaction* transaction, + int64_t id, + int32_t metadataType) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().DeleteMetadata(t->GetManager(), id, metadataType); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode DeleteResource(OrthancPluginDatabaseTransaction* transaction, + int64_t id) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().DeleteResource(t->GetOutput(), t->GetManager(), id); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetAllMetadata(OrthancPluginDatabaseTransaction* transaction, + int64_t id) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::map values; + t->GetBackend().GetAllMetadata(values, t->GetManager(), id); + + for (std::map::const_iterator it = values.begin(); it != values.end(); ++it) + { + t->GetOutput().AnswerMetadata(it->first, it->second); + } + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetAllPublicIds(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginResourceType resourceType) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::list values; + t->GetBackend().GetAllPublicIds(values, t->GetManager(), resourceType); + t->GetOutput().AnswerStrings(values); + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetAllPublicIdsWithLimit(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::list values; + t->GetBackend().GetAllPublicIds(values, t->GetManager(), resourceType, since, limit); + t->GetOutput().AnswerStrings(values); + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetChanges(OrthancPluginDatabaseTransaction* transaction, + uint8_t* targetDone /* out */, + int64_t since, + uint32_t maxResults) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + bool done; + t->GetBackend().GetChanges(t->GetOutput(), done, t->GetManager(), since, maxResults); + *targetDone = (done ? 1 : 0); + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetChildrenInternalId(OrthancPluginDatabaseTransaction* transaction, + int64_t id) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::list values; + t->GetBackend().GetChildrenInternalId(values, t->GetManager(), id); + t->GetOutput().AnswerIntegers64(values); + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetChildrenMetadata(OrthancPluginDatabaseTransaction* transaction, + int64_t resourceId, + int32_t metadata) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::list values; + t->GetBackend().GetChildrenMetadata(values, t->GetManager(), resourceId, metadata); + t->GetOutput().AnswerStrings(values); + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetChildrenPublicId(OrthancPluginDatabaseTransaction* transaction, + int64_t id) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::list values; + t->GetBackend().GetChildrenPublicId(values, t->GetManager(), id); + t->GetOutput().AnswerStrings(values); + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetExportedResources(OrthancPluginDatabaseTransaction* transaction, + uint8_t* targetDone /* out */, + int64_t since, + uint32_t maxResults) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + bool done; + t->GetBackend().GetExportedResources(t->GetOutput(), done, t->GetManager(), since, maxResults); + *targetDone = (done ? 1 : 0); + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetLastChange(OrthancPluginDatabaseTransaction* transaction) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().GetLastChange(t->GetOutput(), t->GetManager()); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetLastChangeIndex(OrthancPluginDatabaseTransaction* transaction, + int64_t* target) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + *target = t->GetBackend().GetLastChangeIndex(t->GetManager()); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetLastExportedResource(OrthancPluginDatabaseTransaction* transaction) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().GetLastExportedResource(t->GetOutput(), t->GetManager()); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetMainDicomTags(OrthancPluginDatabaseTransaction* transaction, + int64_t id) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().GetMainDicomTags(t->GetOutput(), t->GetManager(), id); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetPublicId(OrthancPluginDatabaseTransaction* transaction, + int64_t id) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetOutput().AnswerString(t->GetBackend().GetPublicId(t->GetManager(), id)); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetResourcesCount(OrthancPluginDatabaseTransaction* transaction, + uint64_t* target /* out */, + OrthancPluginResourceType resourceType) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + *target = t->GetBackend().GetResourcesCount(t->GetManager(), resourceType); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetResourceType(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginResourceType* target /* out */, + uint64_t resourceId) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + *target = t->GetBackend().GetResourceType(t->GetManager(), resourceId); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetTotalCompressedSize(OrthancPluginDatabaseTransaction* transaction, + uint64_t* target /* out */) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + *target = t->GetBackend().GetTotalCompressedSize(t->GetManager()); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode GetTotalUncompressedSize(OrthancPluginDatabaseTransaction* transaction, + uint64_t* target /* out */) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + *target = t->GetBackend().GetTotalUncompressedSize(t->GetManager()); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode IsDiskSizeAbove(OrthancPluginDatabaseTransaction* transaction, + uint8_t* target, + uint64_t threshold) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + bool above = (t->GetBackend().GetTotalCompressedSize(t->GetManager()) >= threshold); + *target = (above ? 1 : 0); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode IsExistingResource(OrthancPluginDatabaseTransaction* transaction, + uint8_t* target, + int64_t resourceId) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + bool exists = t->GetBackend().IsExistingResource(t->GetManager(), resourceId); + *target = (exists ? 1 : 0); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode IsProtectedPatient(OrthancPluginDatabaseTransaction* transaction, + uint8_t* target, + int64_t resourceId) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + bool isProtected = t->GetBackend().IsProtectedPatient(t->GetManager(), resourceId); + *target = (isProtected ? 1 : 0); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode ListAvailableAttachments(OrthancPluginDatabaseTransaction* transaction, + int64_t resourceId) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::list values; + t->GetBackend().ListAvailableAttachments(values, t->GetManager(), resourceId); + t->GetOutput().AnswerIntegers32(values); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode LogChange(OrthancPluginDatabaseTransaction* transaction, + int32_t changeType, + int64_t resourceId, + OrthancPluginResourceType resourceType, + const char* date) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().LogChange(t->GetManager(), changeType, resourceId, resourceType, date); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode LogExportedResource(OrthancPluginDatabaseTransaction* transaction, + OrthancPluginResourceType resourceType, + const char* publicId, + const char* modality, + const char* date, + const char* patientId, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + OrthancPluginExportedResource exported; + exported.seq = 0; + exported.resourceType = resourceType; + exported.publicId = publicId; + exported.modality = modality; + exported.date = date; + exported.patientId = patientId; + exported.studyInstanceUid = studyInstanceUid; + exported.seriesInstanceUid = seriesInstanceUid; + exported.sopInstanceUid = sopInstanceUid; + + t->GetOutput().Clear(); + t->GetBackend().LogExportedResource(t->GetManager(), exported); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode LookupAttachment(OrthancPluginDatabaseTransaction* transaction, + int64_t* revision /* out */, + int64_t resourceId, + int32_t contentType) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().LookupAttachment(t->GetOutput(), *revision, t->GetManager(), resourceId, contentType); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode LookupGlobalProperty(OrthancPluginDatabaseTransaction* transaction, + const char* serverIdentifier, + int32_t property) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::string s; + if (t->GetBackend().LookupGlobalProperty(s, t->GetManager(), serverIdentifier, property)) + { + t->GetOutput().AnswerString(s); + } + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode LookupMetadata(OrthancPluginDatabaseTransaction* transaction, + int64_t* revision /* out */, + int64_t id, + int32_t metadata) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::string s; + if (t->GetBackend().LookupMetadata(s, *revision, t->GetManager(), id, metadata)) + { + t->GetOutput().AnswerString(s); + } + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode LookupParent(OrthancPluginDatabaseTransaction* transaction, + uint8_t* existing /* out */, + int64_t* parentId /* out */, + int64_t id) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + if (t->GetBackend().LookupParent(*parentId, t->GetManager(), id)) + { + *existing = 1; + } + else + { + *existing = 0; + } + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode LookupResource(OrthancPluginDatabaseTransaction* transaction, + uint8_t* isExisting /* out */, + int64_t* id /* out */, + OrthancPluginResourceType* type /* out */, + const char* publicId) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + if (t->GetBackend().LookupResource(*id, *type, t->GetManager(), publicId)) + { + *isExisting = 1; + } + else + { + *isExisting = 0; + } + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode LookupResources(OrthancPluginDatabaseTransaction* transaction, + uint32_t constraintsCount, + const OrthancPluginDatabaseConstraint* constraints, + OrthancPluginResourceType queryLevel, + uint32_t limit, + uint8_t requestSomeInstanceId) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::vector lookup; + lookup.reserve(constraintsCount); + + for (uint32_t i = 0; i < constraintsCount; i++) + { + lookup.push_back(Orthanc::DatabaseConstraint(constraints[i])); + } + + t->GetBackend().LookupResources(t->GetOutput(), t->GetManager(), lookup, queryLevel, limit, (requestSomeInstanceId != 0)); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode LookupResourceAndParent(OrthancPluginDatabaseTransaction* transaction, + uint8_t* isExisting /* out */, + int64_t* id /* out */, + OrthancPluginResourceType* type /* out */, + const char* publicId) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + std::string parent; + if (t->GetBackend().LookupResourceAndParent(*id, *type, parent, t->GetManager(), publicId)) + { + *isExisting = 1; + + if (!parent.empty()) + { + t->GetOutput().AnswerString(parent); + } + } + else + { + *isExisting = 0; + } + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode SelectPatientToRecycle(OrthancPluginDatabaseTransaction* transaction, + uint8_t* patientAvailable, + int64_t* patientId) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + if (t->GetBackend().SelectPatientToRecycle(*patientId, t->GetManager())) + { + *patientAvailable = 1; + } + else + { + *patientAvailable = 0; + } + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode SelectPatientToRecycle2(OrthancPluginDatabaseTransaction* transaction, + uint8_t* patientAvailable, + int64_t* patientId, + int64_t patientIdToAvoid) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + + if (t->GetBackend().SelectPatientToRecycle(*patientId, t->GetManager(), patientIdToAvoid)) + { + *patientAvailable = 1; + } + else + { + *patientAvailable = 0; + } + + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode SetGlobalProperty(OrthancPluginDatabaseTransaction* transaction, + const char* serverIdentifier, + int32_t property, + const char* value) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().SetGlobalProperty(t->GetManager(), serverIdentifier, property, value); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode SetMetadata(OrthancPluginDatabaseTransaction* transaction, + int64_t id, + int32_t metadata, + const char* value, + int64_t revision) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().SetMetadata(t->GetManager(), id, metadata, value, revision); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode SetProtectedPatient(OrthancPluginDatabaseTransaction* transaction, + int64_t id, + uint8_t isProtected) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().SetProtectedPatient(t->GetManager(), id, (isProtected != 0)); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + static OrthancPluginErrorCode SetResourcesContent(OrthancPluginDatabaseTransaction* transaction, + uint32_t countIdentifierTags, + const OrthancPluginResourcesContentTags* identifierTags, + uint32_t countMainDicomTags, + const OrthancPluginResourcesContentTags* mainDicomTags, + uint32_t countMetadata, + const OrthancPluginResourcesContentMetadata* metadata) + { + DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); + + try + { + t->GetOutput().Clear(); + t->GetBackend().SetResourcesContent(t->GetManager(), countIdentifierTags, identifierTags, + countMainDicomTags, mainDicomTags, countMetadata, metadata); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); + } + + + void DatabaseBackendAdapterV4::Register(IndexBackend* backend, + size_t countConnections, + unsigned int maxDatabaseRetries) + { + if (isBackendInUse_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + if (backend == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + OrthancPluginDatabaseBackendV4 params; + memset(¶ms, 0, sizeof(params)); + + params.readAnswersCount = ReadAnswersCount; + params.readAnswerAttachment2 = ReadAnswerAttachment2; + params.readAnswerChange = ReadAnswerChange; + params.readAnswerDicomTag = ReadAnswerDicomTag; + params.readAnswerExportedResource = ReadAnswerExportedResource; + params.readAnswerInt32 = ReadAnswerInt32; + params.readAnswerInt64 = ReadAnswerInt64; + params.readAnswerMatchingResource = ReadAnswerMatchingResource; + params.readAnswerMetadata = ReadAnswerMetadata; + params.readAnswerString = ReadAnswerString; + + params.readEventsCount = ReadEventsCount; + params.readEvent2 = ReadEvent2; + + params.open = Open; + params.close = Close; + params.destructDatabase = DestructDatabase; + params.getDatabaseVersion = GetDatabaseVersion; + params.upgradeDatabase = UpgradeDatabase; + params.hasRevisionsSupport = HasRevisionsSupport; + params.hasAttachmentCustomDataSupport = HasAttachmentCustomDataSupport; + params.startTransaction = StartTransaction; + params.destructTransaction = DestructTransaction; + params.rollback = Rollback; + params.commit = Commit; + + params.addAttachment2 = AddAttachment2; + params.clearChanges = ClearChanges; + params.clearExportedResources = ClearExportedResources; + params.clearMainDicomTags = ClearMainDicomTags; + params.createInstance = CreateInstance; + params.deleteAttachment = DeleteAttachment; + params.deleteMetadata = DeleteMetadata; + params.deleteResource = DeleteResource; + params.getAllMetadata = GetAllMetadata; + params.getAllPublicIds = GetAllPublicIds; + params.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit; + params.getChanges = GetChanges; + params.getChildrenInternalId = GetChildrenInternalId; + params.getChildrenMetadata = GetChildrenMetadata; + params.getChildrenPublicId = GetChildrenPublicId; + params.getExportedResources = GetExportedResources; + params.getLastChange = GetLastChange; + params.getLastChangeIndex = GetLastChangeIndex; + params.getLastExportedResource = GetLastExportedResource; + params.getMainDicomTags = GetMainDicomTags; + params.getPublicId = GetPublicId; + params.getResourceType = GetResourceType; + params.getResourcesCount = GetResourcesCount; + params.getTotalCompressedSize = GetTotalCompressedSize; + params.getTotalUncompressedSize = GetTotalUncompressedSize; + params.isDiskSizeAbove = IsDiskSizeAbove; + params.isExistingResource = IsExistingResource; + params.isProtectedPatient = IsProtectedPatient; + params.listAvailableAttachments = ListAvailableAttachments; + params.logChange = LogChange; + params.logExportedResource = LogExportedResource; + params.lookupAttachment = LookupAttachment; + params.lookupGlobalProperty = LookupGlobalProperty; + params.lookupMetadata = LookupMetadata; + params.lookupParent = LookupParent; + params.lookupResource = LookupResource; + params.lookupResourceAndParent = LookupResourceAndParent; + params.lookupResources = LookupResources; + params.selectPatientToRecycle = SelectPatientToRecycle; + params.selectPatientToRecycle2 = SelectPatientToRecycle2; + params.setGlobalProperty = SetGlobalProperty; + params.setMetadata = SetMetadata; + params.setProtectedPatient = SetProtectedPatient; + params.setResourcesContent = SetResourcesContent; + + OrthancPluginContext* context = backend->GetContext(); + + if (OrthancPluginRegisterDatabaseBackendV4( + context, ¶ms, sizeof(params), maxDatabaseRetries, + new Adapter(backend, countConnections)) != OrthancPluginErrorCode_Success) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to register the database backend"); + } + + backend->SetOutputFactory(new Factory); + + isBackendInUse_ = true; + } + + + void DatabaseBackendAdapterV4::Finalize() + { + if (isBackendInUse_) + { + fprintf(stderr, "The Orthanc core has not destructed the index backend, internal error\n"); + } + } +} + +# endif +#endif diff -r 7671fa7f099e -r cd9521e04249 Framework/Plugins/DatabaseBackendAdapterV4.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.h Thu Sep 15 18:12:34 2022 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2021 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + + +#pragma once + +#include "IndexBackend.h" + + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + +namespace OrthancDatabases +{ + /** + * @brief Bridge between C and C++ database engines. + * + * Class creating the bridge between the C low-level primitives for + * custom database engines, and the high-level IDatabaseBackend C++ + * interface, for Orthanc >= 1.12.0. + **/ + class DatabaseBackendAdapterV4 + { + private: + class Output; + + // This class cannot be instantiated + DatabaseBackendAdapterV4() + { + } + + public: + class Adapter; + class Transaction; + + class Factory : public IDatabaseBackendOutput::IFactory + { + public: + virtual IDatabaseBackendOutput* CreateOutput() ORTHANC_OVERRIDE; + }; + + static void Register(IndexBackend* backend, + size_t countConnections, + unsigned int maxDatabaseRetries); + + static void Finalize(); + }; +} + +# endif +#endif diff -r 7671fa7f099e -r cd9521e04249 Framework/Plugins/IDatabaseBackend.h --- a/Framework/Plugins/IDatabaseBackend.h Tue Jul 05 08:44:26 2022 +0200 +++ b/Framework/Plugins/IDatabaseBackend.h Thu Sep 15 18:12:34 2022 +0200 @@ -50,11 +50,21 @@ virtual bool HasRevisionsSupport() const = 0; + virtual bool HasAttachmentCustomDataSupport() const = 0; + virtual void AddAttachment(DatabaseManager& manager, int64_t id, const OrthancPluginAttachment& attachment, int64_t revision) = 0; +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + + virtual void AddAttachment2(DatabaseManager& manager, + int64_t id, + const OrthancPluginAttachment2& attachment, + int64_t revision) = 0; +#endif + virtual void AttachChild(DatabaseManager& manager, int64_t parent, int64_t child) = 0; diff -r 7671fa7f099e -r cd9521e04249 Framework/Plugins/IDatabaseBackendOutput.h --- a/Framework/Plugins/IDatabaseBackendOutput.h Tue Jul 05 08:44:26 2022 +0200 +++ b/Framework/Plugins/IDatabaseBackendOutput.h Thu Sep 15 18:12:34 2022 +0200 @@ -54,7 +54,8 @@ const std::string& uncompressedHash, int32_t compressionType, uint64_t compressedSize, - const std::string& compressedHash) = 0; + const std::string& compressedHash, + const std::string& customData) = 0; virtual void SignalDeletedResource(const std::string& publicId, OrthancPluginResourceType resourceType) = 0; @@ -68,7 +69,8 @@ const std::string& uncompressedHash, int32_t compressionType, uint64_t compressedSize, - const std::string& compressedHash) = 0; + const std::string& compressedHash, + const std::string& customData) = 0; virtual void AnswerChange(int64_t seq, int32_t changeType, diff -r 7671fa7f099e -r cd9521e04249 Framework/Plugins/IndexBackend.cpp --- a/Framework/Plugins/IndexBackend.cpp Tue Jul 05 08:44:26 2022 +0200 +++ b/Framework/Plugins/IndexBackend.cpp Thu Sep 15 18:12:34 2022 +0200 @@ -27,6 +27,7 @@ #include "../Common/Utf8StringValue.h" #include "DatabaseBackendAdapterV2.h" #include "DatabaseBackendAdapterV3.h" +#include "DatabaseBackendAdapterV4.h" #include "GlobalProperties.h" #include // For std::unique_ptr<> @@ -202,7 +203,7 @@ DatabaseManager::CachedStatement statement( STATEMENT_FROM_HERE, manager, "SELECT uuid, fileType, uncompressedSize, uncompressedHash, compressionType, " - "compressedSize, compressedHash FROM DeletedFiles"); + "compressedSize, compressedHash, revision, customData FROM DeletedFiles"); statement.SetReadOnly(true); statement.Execute(); @@ -215,7 +216,8 @@ statement.ReadString(3), statement.ReadInteger32(4), statement.ReadInteger64(5), - statement.ReadString(6)); + statement.ReadString(6), + statement.ReadString(8)); statement.Next(); } @@ -282,12 +284,25 @@ } } - - static void ExecuteAddAttachment(DatabaseManager::CachedStatement& statement, - Dictionary& args, + static void ExecuteAddAttachment(DatabaseManager& manager, int64_t id, - const OrthancPluginAttachment& attachment) + const char* uuid, + int32_t contentType, + uint64_t uncompressedSize, + const char* uncompressedHash, + int32_t compressionType, + uint64_t compressedSize, + const char* compressedHash, + const char* customData, + int64_t revision) { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "INSERT INTO AttachedFiles VALUES(${id}, ${type}, ${uuid}, ${compressed}, " + "${uncompressed}, ${compression}, ${hash}, ${hash-compressed}, ${revision}, ${custom-data})"); + + Dictionary args; + statement.SetParameterType("id", ValueType_Integer64); statement.SetParameterType("type", ValueType_Integer64); statement.SetParameterType("uuid", ValueType_Utf8String); @@ -296,51 +311,45 @@ statement.SetParameterType("compression", ValueType_Integer64); statement.SetParameterType("hash", ValueType_Utf8String); statement.SetParameterType("hash-compressed", ValueType_Utf8String); + statement.SetParameterType("revision", ValueType_Integer64); + statement.SetParameterType("custom-data", ValueType_Utf8String); args.SetIntegerValue("id", id); - args.SetIntegerValue("type", attachment.contentType); - args.SetUtf8Value("uuid", attachment.uuid); - args.SetIntegerValue("compressed", attachment.compressedSize); - args.SetIntegerValue("uncompressed", attachment.uncompressedSize); - args.SetIntegerValue("compression", attachment.compressionType); - args.SetUtf8Value("hash", attachment.uncompressedHash); - args.SetUtf8Value("hash-compressed", attachment.compressedHash); + args.SetIntegerValue("type", contentType); + args.SetUtf8Value("uuid", uuid); + args.SetIntegerValue("compressed", compressedSize); + args.SetIntegerValue("uncompressed", uncompressedSize); + args.SetIntegerValue("compression", compressionType); + args.SetUtf8Value("hash", uncompressedHash); + args.SetUtf8Value("hash-compressed", compressedHash); + args.SetIntegerValue("revision", revision); + args.SetUtf8Value("custom-data", customData); statement.Execute(args); } - + void IndexBackend::AddAttachment(DatabaseManager& manager, int64_t id, const OrthancPluginAttachment& attachment, int64_t revision) { - if (HasRevisionsSupport()) - { - DatabaseManager::CachedStatement statement( - STATEMENT_FROM_HERE, manager, - "INSERT INTO AttachedFiles VALUES(${id}, ${type}, ${uuid}, ${compressed}, " - "${uncompressed}, ${compression}, ${hash}, ${hash-compressed}, ${revision})"); - - Dictionary args; - - statement.SetParameterType("revision", ValueType_Integer64); - args.SetIntegerValue("revision", revision); - - ExecuteAddAttachment(statement, args, id, attachment); - } - else - { - DatabaseManager::CachedStatement statement( - STATEMENT_FROM_HERE, manager, - "INSERT INTO AttachedFiles VALUES(${id}, ${type}, ${uuid}, ${compressed}, " - "${uncompressed}, ${compression}, ${hash}, ${hash-compressed})"); - - Dictionary args; - ExecuteAddAttachment(statement, args, id, attachment); - } + assert(HasRevisionsSupport() && HasAttachmentCustomDataSupport()); // all plugins supports these features now + ExecuteAddAttachment(manager, id, attachment.uuid, attachment.contentType, attachment.uncompressedSize, attachment.uncompressedHash, + attachment.compressionType, attachment.compressedSize, attachment.compressedHash, "", revision); } +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + void IndexBackend::AddAttachment2(DatabaseManager& manager, + int64_t id, + const OrthancPluginAttachment2& attachment, + int64_t revision) + { + assert(HasRevisionsSupport() && HasAttachmentCustomDataSupport()); // all plugins supports these features now + ExecuteAddAttachment(manager, id, attachment.uuid, attachment.contentType, attachment.uncompressedSize, attachment.uncompressedHash, + attachment.compressionType, attachment.compressedSize, attachment.compressedHash, attachment.customData, revision); + } +#endif void IndexBackend::AttachChild(DatabaseManager& manager, int64_t parent, @@ -1037,12 +1046,21 @@ statement.Execute(args); } - - static bool ExecuteLookupAttachment(DatabaseManager::CachedStatement& statement, - IDatabaseBackendOutput& output, + + /* Use GetOutput().AnswerAttachment() */ + bool IndexBackend::LookupAttachment(IDatabaseBackendOutput& output, + int64_t& revision /*out*/, + DatabaseManager& manager, int64_t id, int32_t contentType) { + assert(HasRevisionsSupport() && HasAttachmentCustomDataSupport()); // we force v4 plugins to support both ! + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedHash, " + "compressedHash, revision, customData FROM AttachedFiles WHERE id=${id} AND fileType=${type}"); + + statement.SetReadOnly(true); statement.SetParameterType("id", ValueType_Integer64); statement.SetParameterType("type", ValueType_Integer64); @@ -1059,64 +1077,35 @@ } else { + if (statement.GetResultField(6).GetType() == ValueType_Null) + { + // "NULL" can happen with a database created by PostgreSQL + // plugin <= 3.3 (because of "ALTER TABLE AttachedFiles") + revision = 0; + } + else + { + revision = statement.ReadInteger64(6); + } + + std::string customData; + if (statement.GetResultField(7).GetType() == ValueType_Utf8String) // column has been added in 1.12.0 + { + customData = statement.ReadString(7); + } + + output.AnswerAttachment(statement.ReadString(0), contentType, statement.ReadInteger64(1), statement.ReadString(4), statement.ReadInteger32(2), statement.ReadInteger64(3), - statement.ReadString(5)); + statement.ReadString(5), + customData); return true; } - } - - - - /* Use GetOutput().AnswerAttachment() */ - bool IndexBackend::LookupAttachment(IDatabaseBackendOutput& output, - int64_t& revision /*out*/, - DatabaseManager& manager, - int64_t id, - int32_t contentType) - { - if (HasRevisionsSupport()) - { - DatabaseManager::CachedStatement statement( - STATEMENT_FROM_HERE, manager, - "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedHash, " - "compressedHash, revision FROM AttachedFiles WHERE id=${id} AND fileType=${type}"); - - if (ExecuteLookupAttachment(statement, output, id, contentType)) - { - if (statement.GetResultField(6).GetType() == ValueType_Null) - { - // "NULL" can happen with a database created by PostgreSQL - // plugin <= 3.3 (because of "ALTER TABLE AttachedFiles") - revision = 0; - } - else - { - revision = statement.ReadInteger64(6); - } - - return true; - } - else - { - return false; - } - } - else - { - DatabaseManager::CachedStatement statement( - STATEMENT_FROM_HERE, manager, - "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedHash, " - "compressedHash FROM AttachedFiles WHERE id=${id} AND fileType=${type}"); - - revision = 0; - - return ExecuteLookupAttachment(statement, output, id, contentType); - } + } @@ -2609,22 +2598,31 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } - bool hasLoadedV3 = false; + bool hasLoadedV3OrAbove = false; #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 -# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + if (OrthancPluginCheckVersionAdvanced(backend->GetContext(), 1, 12, 0) == 1) + { + LOG(WARNING) << "The index plugin will use " << countConnections << " connection(s) to the database, " + << "and will retry up to " << maxDatabaseRetries << " time(s) in the case of a collision"; + + OrthancDatabases::DatabaseBackendAdapterV4::Register(backend, countConnections, maxDatabaseRetries); + hasLoadedV3OrAbove = true; + } +# elif ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) if (OrthancPluginCheckVersionAdvanced(backend->GetContext(), 1, 9, 2) == 1) { LOG(WARNING) << "The index plugin will use " << countConnections << " connection(s) to the database, " << "and will retry up to " << maxDatabaseRetries << " time(s) in the case of a collision"; OrthancDatabases::DatabaseBackendAdapterV3::Register(backend, countConnections, maxDatabaseRetries); - hasLoadedV3 = true; + hasLoadedV3OrAbove = true; } # endif #endif - if (!hasLoadedV3) + if (!hasLoadedV3OrAbove) { LOG(WARNING) << "Performance warning: Your version of the Orthanc core or SDK doesn't support multiple readers/writers"; OrthancDatabases::DatabaseBackendAdapterV2::Register(backend); diff -r 7671fa7f099e -r cd9521e04249 Framework/Plugins/IndexBackend.h --- a/Framework/Plugins/IndexBackend.h Tue Jul 05 08:44:26 2022 +0200 +++ b/Framework/Plugins/IndexBackend.h Thu Sep 15 18:12:34 2022 +0200 @@ -85,7 +85,16 @@ int64_t id, const OrthancPluginAttachment& attachment, int64_t revision) ORTHANC_OVERRIDE; - + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + + virtual void AddAttachment2(DatabaseManager& manager, + int64_t id, + const OrthancPluginAttachment2& attachment, + int64_t revision) ORTHANC_OVERRIDE; + +#endif + virtual void AttachChild(DatabaseManager& manager, int64_t parent, int64_t child) ORTHANC_OVERRIDE; diff -r 7671fa7f099e -r cd9521e04249 MySQL/CMakeLists.txt --- a/MySQL/CMakeLists.txt Tue Jul 05 08:44:26 2022 +0200 +++ b/MySQL/CMakeLists.txt Thu Sep 15 18:12:34 2022 +0200 @@ -78,6 +78,9 @@ MYSQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql MYSQL_GET_LAST_CHANGE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/GetLastChangeIndex.sql MYSQL_CREATE_INSTANCE ${CMAKE_SOURCE_DIR}/Plugins/CreateInstance.sql + + MYSQL_INSTALL_REVISION_AND_CUSTOM_DATA + ${CMAKE_SOURCE_DIR}/Plugins/InstallRevisionAndCustomData.sql ) add_custom_target( diff -r 7671fa7f099e -r cd9521e04249 MySQL/NEWS --- a/MySQL/NEWS Tue Jul 05 08:44:26 2022 +0200 +++ b/MySQL/NEWS Thu Sep 15 18:12:34 2022 +0200 @@ -1,6 +1,9 @@ Pending changes in the mainline =============================== +* Added support for customData in AttachedFiles +* Added support for revision in AttachedFiles & Metadata + Release 4.3 (2021-07-22) ======================== diff -r 7671fa7f099e -r cd9521e04249 MySQL/Plugins/InstallRevisionAndCustomData.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySQL/Plugins/InstallRevisionAndCustomData.sql Thu Sep 15 18:12:34 2022 +0200 @@ -0,0 +1,28 @@ +ALTER TABLE AttachedFiles ADD COLUMN revision INTEGER; +ALTER TABLE DeletedFiles ADD COLUMN revision INTEGER; +ALTER TABLE Metadata ADD COLUMN revision INTEGER; + +ALTER TABLE AttachedFiles ADD COLUMN customData LONGTEXT; +ALTER TABLE DeletedFiles ADD COLUMN customData LONGTEXT; + +DROP TRIGGER AttachedFileDeleted; + +CREATE TRIGGER AttachedFileDeleted +AFTER DELETE ON AttachedFiles +FOR EACH ROW + BEGIN + INSERT INTO DeletedFiles VALUES(old.uuid, old.filetype, old.compressedSize, + old.uncompressedSize, old.compressionType, + old.uncompressedHash, old.compressedHash, + old.revision, old.customData)@ + END; + + +DROP TRIGGER ResourceDeleted; + +CREATE TRIGGER ResourceDeleted +BEFORE DELETE ON Resources +FOR EACH ROW + BEGIN + INSERT INTO DeletedFiles SELECT uuid, fileType, compressedSize, uncompressedSize, compressionType, uncompressedHash, compressedHash, revision, customData FROM AttachedFiles WHERE id=old.internalId@ + END; \ No newline at end of file diff -r 7671fa7f099e -r cd9521e04249 MySQL/Plugins/MySQLIndex.cpp --- a/MySQL/Plugins/MySQLIndex.cpp Tue Jul 05 08:44:26 2022 +0200 +++ b/MySQL/Plugins/MySQLIndex.cpp Thu Sep 15 18:12:34 2022 +0200 @@ -294,7 +294,26 @@ t.Commit(); } - if (revision != 6) + if (revision == 6) + { + DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); + + // Install revision and customData extension + std::string query; + + Orthanc::EmbeddedResources::GetFileResource + (query, Orthanc::EmbeddedResources::MYSQL_INSTALL_REVISION_AND_CUSTOM_DATA); + + // Need to escape arobases: Don't use "t.GetDatabaseTransaction().ExecuteMultiLines()" here + db.ExecuteMultiLines(query, true); + + revision = 7; + SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, revision); + + t.Commit(); + } + + if (revision != 7) { LOG(ERROR) << "MySQL plugin is incompatible with database schema revision: " << revision; throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); diff -r 7671fa7f099e -r cd9521e04249 MySQL/Plugins/MySQLIndex.h --- a/MySQL/Plugins/MySQLIndex.h Tue Jul 05 08:44:26 2022 +0200 +++ b/MySQL/Plugins/MySQLIndex.h Thu Sep 15 18:12:34 2022 +0200 @@ -47,7 +47,12 @@ virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE { - return false; // TODO - REVISIONS + return true; + } + + virtual bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE + { + return true; } virtual int64_t CreateResource(DatabaseManager& manager, diff -r 7671fa7f099e -r cd9521e04249 MySQL/Plugins/PrepareIndex.sql --- a/MySQL/Plugins/PrepareIndex.sql Tue Jul 05 08:44:26 2022 +0200 +++ b/MySQL/Plugins/PrepareIndex.sql Thu Sep 15 18:12:34 2022 +0200 @@ -48,6 +48,8 @@ compressionType INTEGER, uncompressedHash VARCHAR(40), compressedHash VARCHAR(40), + -- revision INTEGER, -- new in v 4.X, added in MySQLIndex::ConfigureDatabase + -- customData LONGTEXT, -- new in v 4.X, added in MySQLIndex::ConfigureDatabase PRIMARY KEY(id, fileType), CONSTRAINT AttachedFiles1 FOREIGN KEY (id) REFERENCES Resources(internalId) ON DELETE CASCADE ); @@ -104,6 +106,8 @@ compressionType INTEGER, -- 4 uncompressedHash VARCHAR(40), -- 5 compressedHash VARCHAR(40) -- 6 + -- revision INTEGER, -- new in v 4.X, added in MySQLIndex::ConfigureDatabase + -- customData LONGTEXT, -- new in v 4.X, added in MySQLIndex::ConfigureDatabase ); -- End of differences @@ -119,6 +123,8 @@ INSERT INTO DeletedFiles VALUES(old.uuid, old.filetype, old.compressedSize, old.uncompressedSize, old.compressionType, old.uncompressedHash, old.compressedHash)@ + -- old.revision, old.customData -- new in v 4.X, added in MySQLIndex::ConfigureDatabase + END; @@ -127,6 +133,7 @@ FOR EACH ROW BEGIN INSERT INTO DeletedFiles SELECT uuid, fileType, compressedSize, uncompressedSize, compressionType, uncompressedHash, compressedHash FROM AttachedFiles WHERE id=old.internalId@ + -- revision, customData -- new in v 4.X, added in MySQLIndex::ConfigureDatabase END; diff -r 7671fa7f099e -r cd9521e04249 Odbc/NEWS --- a/Odbc/NEWS Tue Jul 05 08:44:26 2022 +0200 +++ b/Odbc/NEWS Thu Sep 15 18:12:34 2022 +0200 @@ -1,3 +1,8 @@ +Pending changes in the mainline +=============================== + +* Added support for customData in AttachedFiles + Release 1.1 (2021-12-06) ======================== diff -r 7671fa7f099e -r cd9521e04249 Odbc/Plugins/OdbcIndex.cpp --- a/Odbc/Plugins/OdbcIndex.cpp Tue Jul 05 08:44:26 2022 +0200 +++ b/Odbc/Plugins/OdbcIndex.cpp Thu Sep 15 18:12:34 2022 +0200 @@ -160,6 +160,49 @@ return OdbcDatabase::CreateDatabaseFactory(maxConnectionRetries_, connectionRetryInterval_, connectionString_, true); } + static void AdaptTypesToDialect(std::string& sql, Dialect dialect) + { + switch (dialect) + { + case Dialect_SQLite: + boost::replace_all(sql, "${LONGTEXT}", "TEXT"); + boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT"); + boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", "NULL, "); + break; + + case Dialect_PostgreSQL: + boost::replace_all(sql, "${LONGTEXT}", "TEXT"); + boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "BIGSERIAL NOT NULL PRIMARY KEY"); + boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", "DEFAULT, "); + break; + + case Dialect_MySQL: + boost::replace_all(sql, "${LONGTEXT}", "LONGTEXT"); + boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY"); + boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", "NULL, "); + break; + + case Dialect_MSSQL: + /** + * cf. OMSSQL-5: Use VARCHAR(MAX) instead of TEXT: (1) + * Microsoft issued a warning stating that "ntext, text, and + * image data types will be removed in a future version of + * SQL Server" + * (https://msdn.microsoft.com/en-us/library/ms187993.aspx), + * and (2) SQL Server does not support comparison of TEXT + * with '=' operator (e.g. in WHERE statements such as + * IndexBackend::LookupIdentifier())." + **/ + boost::replace_all(sql, "${LONGTEXT}", "VARCHAR(MAX)"); + boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "BIGINT IDENTITY NOT NULL PRIMARY KEY"); + boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", ""); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + void OdbcIndex::ConfigureDatabase(DatabaseManager& manager) { @@ -185,46 +228,8 @@ { std::string sql; Orthanc::EmbeddedResources::GetFileResource(sql, Orthanc::EmbeddedResources::ODBC_PREPARE_INDEX); - - switch (db.GetDialect()) - { - case Dialect_SQLite: - boost::replace_all(sql, "${LONGTEXT}", "TEXT"); - boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT"); - boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", "NULL, "); - break; - - case Dialect_PostgreSQL: - boost::replace_all(sql, "${LONGTEXT}", "TEXT"); - boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "BIGSERIAL NOT NULL PRIMARY KEY"); - boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", "DEFAULT, "); - break; - - case Dialect_MySQL: - boost::replace_all(sql, "${LONGTEXT}", "LONGTEXT"); - boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY"); - boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", "NULL, "); - break; - - case Dialect_MSSQL: - /** - * cf. OMSSQL-5: Use VARCHAR(MAX) instead of TEXT: (1) - * Microsoft issued a warning stating that "ntext, text, and - * image data types will be removed in a future version of - * SQL Server" - * (https://msdn.microsoft.com/en-us/library/ms187993.aspx), - * and (2) SQL Server does not support comparison of TEXT - * with '=' operator (e.g. in WHERE statements such as - * IndexBackend::LookupIdentifier())." - **/ - boost::replace_all(sql, "${LONGTEXT}", "VARCHAR(MAX)"); - boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "BIGINT IDENTITY NOT NULL PRIMARY KEY"); - boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", ""); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } + + AdaptTypesToDialect(sql, db.GetDialect()); { DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); @@ -238,6 +243,22 @@ db.ExecuteMultiLines("ALTER DATABASE CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); } + { // v 4.X: add customData + int patchLevel; + + if (!LookupGlobalIntegerProperty(patchLevel, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel)) + { + std::string sqlAddCustomData = "ALTER TABLE AttachedFiles ADD customData ${LONGTEXT};" + "ALTER TABLE DeletedFiles ADD customData ${LONGTEXT}"; + + AdaptTypesToDialect(sqlAddCustomData, db.GetDialect()); + + db.ExecuteMultiLines(sqlAddCustomData); + + SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, 1); + } + } + t.Commit(); } } diff -r 7671fa7f099e -r cd9521e04249 Odbc/Plugins/OdbcIndex.h --- a/Odbc/Plugins/OdbcIndex.h Tue Jul 05 08:44:26 2022 +0200 +++ b/Odbc/Plugins/OdbcIndex.h Thu Sep 15 18:12:34 2022 +0200 @@ -62,6 +62,11 @@ return true; } + bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE + { + return true; + } + virtual int64_t CreateResource(DatabaseManager& manager, const char* publicId, OrthancPluginResourceType type) ORTHANC_OVERRIDE; diff -r 7671fa7f099e -r cd9521e04249 PostgreSQL/NEWS --- a/PostgreSQL/NEWS Tue Jul 05 08:44:26 2022 +0200 +++ b/PostgreSQL/NEWS Thu Sep 15 18:12:34 2022 +0200 @@ -1,6 +1,8 @@ Pending changes in the mainline =============================== +* Added support for customData in AttachedFiles + Release 4.0 (2021-04-22) ======================== diff -r 7671fa7f099e -r cd9521e04249 PostgreSQL/Plugins/PostgreSQLIndex.cpp --- a/PostgreSQL/Plugins/PostgreSQLIndex.cpp Tue Jul 05 08:44:26 2022 +0200 +++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp Thu Sep 15 18:12:34 2022 +0200 @@ -294,6 +294,49 @@ t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE AttachedFiles ADD COLUMN revision INTEGER"); } + // new in v 4.X + if (!db.DoesColumnExist("DeletedFiles", "revision")) + { + t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE DeletedFiles ADD COLUMN revision INTEGER"); + } + + if (!db.DoesColumnExist("AttachedFiles", "customData")) + { + t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE AttachedFiles ADD COLUMN customData TEXT"); + } + + if (!db.DoesColumnExist("DeletedFiles", "customData")) + { + // add the column and modify the trigger + t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE DeletedFiles ADD COLUMN customData TEXT"); + + t.GetDatabaseTransaction().ExecuteMultiLines( + "DROP TRIGGER AttachedFileDeleted ON AttachedFiles"); + + t.GetDatabaseTransaction().ExecuteMultiLines( + "DROP FUNCTION AttachedFileDeletedFunc"); + + t.GetDatabaseTransaction().ExecuteMultiLines( + "CREATE FUNCTION AttachedFileDeletedFunc() " + "RETURNS TRIGGER AS $body$" + "BEGIN" + " INSERT INTO DeletedFiles VALUES" + " (old.uuid, old.filetype, old.compressedSize," + " old.uncompressedSize, old.compressionType," + " old.uncompressedHash, old.compressedHash," + " old.revision, old.customData);" + " RETURN NULL;" + "END;" + "$body$ LANGUAGE plpgsql;"); + + t.GetDatabaseTransaction().ExecuteMultiLines( + "CREATE TRIGGER AttachedFileDeleted " + "AFTER DELETE ON AttachedFiles " + "FOR EACH ROW " + "EXECUTE PROCEDURE AttachedFileDeletedFunc();" + ); + } + t.Commit(); } } diff -r 7671fa7f099e -r cd9521e04249 PostgreSQL/Plugins/PostgreSQLIndex.h --- a/PostgreSQL/Plugins/PostgreSQLIndex.h Tue Jul 05 08:44:26 2022 +0200 +++ b/PostgreSQL/Plugins/PostgreSQLIndex.h Thu Sep 15 18:12:34 2022 +0200 @@ -50,6 +50,11 @@ return true; } + virtual bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE + { + return true; + } + virtual int64_t CreateResource(DatabaseManager& manager, const char* publicId, OrthancPluginResourceType type) diff -r 7671fa7f099e -r cd9521e04249 PostgreSQL/Plugins/PrepareIndex.sql --- a/PostgreSQL/Plugins/PrepareIndex.sql Tue Jul 05 08:44:26 2022 +0200 +++ b/PostgreSQL/Plugins/PrepareIndex.sql Thu Sep 15 18:12:34 2022 +0200 @@ -30,6 +30,7 @@ id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, type INTEGER NOT NULL, value TEXT, + -- revision INTEGER, -- new in v4.0 (this column is added in PostgreSQLIndex::ConfigureDatabase) PRIMARY KEY(id, type) ); @@ -42,6 +43,8 @@ compressionType INTEGER, uncompressedHash VARCHAR(40), compressedHash VARCHAR(40), + -- revision BIGINT, -- new in v 4.0 (this column is added in PostgreSQLIndex::ConfigureDatabase) + -- customData TEXT, -- new in v 4.X (this column is added in PostgreSQLIndex::ConfigureDatabase) PRIMARY KEY(id, fileType) ); @@ -112,7 +115,9 @@ INSERT INTO DeletedFiles VALUES (old.uuid, old.filetype, old.compressedSize, old.uncompressedSize, old.compressionType, - old.uncompressedHash, old.compressedHash); + old.uncompressedHash, old.compressedHash + -- old.customData -- new in v 4.X (this column is added in PostgreSQLIndex::ConfigureDatabase) + ); RETURN NULL; END; $body$ LANGUAGE plpgsql; diff -r 7671fa7f099e -r cd9521e04249 README --- a/README Tue Jul 05 08:44:26 2022 +0200 +++ b/README Thu Sep 15 18:12:34 2022 +0200 @@ -49,6 +49,69 @@ https://hg.orthanc-server.com/orthanc-postgresql/ +Development +----------- + +PostgreSQL +========== + +To quickly start a test PG server: + + docker run -p 5432:5432 --rm --env POSTGRES_HOST_AUTH_METHOD=trust postgres:13.4 + +And use this Orthanc configuration: + "PostgreSQL": { + "EnableIndex": true, + "EnableStorage": false, // DICOM files are stored in the Orthanc container in /var/lib/orthanc/db/ + "Host": "localhost", // the name of the PostgreSQL container + "Database": "postgres", // default database name in PostgreSQL container (no need to create it) + "Username": "postgres", // default user name in PostgreSQL container (no need to create it) + "Password": "postgres" + }, + +MySQL +===== + +To quickly start a test MySQL server: + + docker run -p 3306:3306 --rm --env MYSQL_PASSWORD=orthanc --env MYSQL_USER=orthanc --env MYSQL_DATABASE=orthanc --env MYSQL_ROOT_PASSWORD=pwd-root mysql:8.0 mysqld --default-authentication-plugin=mysql_native_password --log-bin-trust-function-creators=1 + +And use this Orthanc configuration: + "MySQL": { + "EnableIndex": true, + "EnableStorage": false, + "Host": "localhost", + "Database": "orthanc", + "Username": "orthanc", + "Password": "orthanc", + "UnixSocket": "" + }, + + +ODBC (SQL Server) +================= + +To quickly start a test MySQL server: + + docker run -e "ACCEPT_EULA=Y" --rm --env "SA_PASSWORD=yourStrong-Password" --entrypoint=bash -it -p 1433:1433 mcr.microsoft.com/mssql/server:2019-latest + +Then: + (sleep 15s && /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P yourStrong-Password -Q 'CREATE DATABASE orthanctest') & /opt/mssql/bin/sqlservr + +And use this Orthanc configuration: + "Odbc" : { + "IndexConnectionString": "Driver={ODBC Driver 17 for SQL Server};Server=tcp:localhost,1433;Database=orthanctest;Uid=sa;Pwd=yourStrong-Password;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=30;", + "EnableIndex": true, + "EnableStorage": false + } + + +SQLite +====== + +To quickly test the SQLite plugin, simply run orthanc and load the plugin (no configuration required). + + Licensing --------- diff -r 7671fa7f099e -r cd9521e04249 Resources/CMake/DatabasesPluginConfiguration.cmake --- a/Resources/CMake/DatabasesPluginConfiguration.cmake Tue Jul 05 08:44:26 2022 +0200 +++ b/Resources/CMake/DatabasesPluginConfiguration.cmake Thu Sep 15 18:12:34 2022 +0200 @@ -79,6 +79,7 @@ ${ORTHANC_CORE_SOURCES} ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV2.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV3.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV4.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/IndexBackend.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/StorageBackend.cpp ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Databases/DatabaseConstraint.cpp diff -r 7671fa7f099e -r cd9521e04249 SQLite/CMakeLists.txt --- a/SQLite/CMakeLists.txt Tue Jul 05 08:44:26 2022 +0200 +++ b/SQLite/CMakeLists.txt Thu Sep 15 18:12:34 2022 +0200 @@ -42,6 +42,7 @@ EmbedResources( SQLITE_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql + SQLITE_INSTALL_CUSTOM_DATA ${CMAKE_SOURCE_DIR}/Plugins/InstallCustomData.sql ) add_custom_target( diff -r 7671fa7f099e -r cd9521e04249 SQLite/NEWS --- a/SQLite/NEWS Tue Jul 05 08:44:26 2022 +0200 +++ b/SQLite/NEWS Thu Sep 15 18:12:34 2022 +0200 @@ -1,3 +1,9 @@ +Pending changes in the mainline +=============================== + +* Added support for customData in AttachedFiles + + Pending changes in the mainline =============================== diff -r 7671fa7f099e -r cd9521e04249 SQLite/Plugins/InstallCustomData.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SQLite/Plugins/InstallCustomData.sql Thu Sep 15 18:12:34 2022 +0200 @@ -0,0 +1,17 @@ + +-- Add new column for customData +ALTER TABLE AttachedFiles ADD COLUMN customData TEXT; +ALTER TABLE DeletedFiles ADD COLUMN revision INTEGER; +ALTER TABLE DeletedFiles ADD COLUMN customData TEXT; + + +DROP TRIGGER AttachedFileDeleted; + +CREATE TRIGGER AttachedFileDeleted +AFTER DELETE ON AttachedFiles +BEGIN + INSERT INTO DeletedFiles VALUES(old.uuid, old.filetype, old.compressedSize, + old.uncompressedSize, old.compressionType, + old.uncompressedHash, old.compressedHash, + old.revision, old.customData); +END; diff -r 7671fa7f099e -r cd9521e04249 SQLite/Plugins/SQLiteIndex.cpp --- a/SQLite/Plugins/SQLiteIndex.cpp Tue Jul 05 08:44:26 2022 +0200 +++ b/SQLite/Plugins/SQLiteIndex.cpp Thu Sep 15 18:12:34 2022 +0200 @@ -145,7 +145,22 @@ SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, revision); } - if (revision != 1) + // install customData + if (!LookupGlobalIntegerProperty(revision, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel) + || revision == 1) + { + std::string query; + + Orthanc::EmbeddedResources::GetFileResource + (query, Orthanc::EmbeddedResources::SQLITE_INSTALL_CUSTOM_DATA); + + t.GetDatabaseTransaction().ExecuteMultiLines(query); + + revision = 2; + SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, revision); + } + + if (revision != 2) { LOG(ERROR) << "SQLite plugin is incompatible with database schema revision: " << revision; throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); diff -r 7671fa7f099e -r cd9521e04249 SQLite/Plugins/SQLiteIndex.h --- a/SQLite/Plugins/SQLiteIndex.h Tue Jul 05 08:44:26 2022 +0200 +++ b/SQLite/Plugins/SQLiteIndex.h Thu Sep 15 18:12:34 2022 +0200 @@ -51,6 +51,11 @@ return true; } + bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE + { + return true; + } + virtual int64_t CreateResource(DatabaseManager& manager, const char* publicId, OrthancPluginResourceType type) ORTHANC_OVERRIDE;