Mercurial > hg > orthanc-databases
diff Framework/Plugins/IndexBackend.cpp @ 0:7cea966b6829
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 04 Jul 2018 08:16:29 +0200 |
parents | |
children | d17b2631bb67 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/IndexBackend.cpp Wed Jul 04 08:16:29 2018 +0200 @@ -0,0 +1,1580 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "IndexBackend.h" + +#include "../Common/BinaryStringValue.h" +#include "../Common/Integer64Value.h" +#include "../Common/Utf8StringValue.h" +#include "GlobalProperties.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> +#include <OrthancServer/ServerEnumerations.h> + + +namespace OrthancDatabases +{ + static std::string ConvertWildcardToLike(const std::string& query) + { + std::string s = query; + + for (size_t i = 0; i < s.size(); i++) + { + if (s[i] == '*') + { + s[i] = '%'; + } + else if (s[i] == '?') + { + s[i] = '_'; + } + } + + return s; + } + + + int64_t IndexBackend::ReadInteger64(const DatabaseManager::CachedStatement& statement, + size_t field) + { + if (statement.IsDone()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); + } + + const IValue& value = statement.GetResultField(field); + + switch (value.GetType()) + { + case ValueType_Integer64: + return dynamic_cast<const Integer64Value&>(value).GetValue(); + + default: + //LOG(ERROR) << value.Format(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + int32_t IndexBackend::ReadInteger32(const DatabaseManager::CachedStatement& statement, + size_t field) + { + if (statement.IsDone()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); + } + + int64_t value = ReadInteger64(statement, field); + + if (value != static_cast<int64_t>(static_cast<int32_t>(value))) + { + LOG(ERROR) << "Integer overflow"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + return static_cast<int32_t>(value); + } + } + + + std::string IndexBackend::ReadString(const DatabaseManager::CachedStatement& statement, + size_t field) + { + const IValue& value = statement.GetResultField(field); + + switch (value.GetType()) + { + case ValueType_BinaryString: + return dynamic_cast<const BinaryStringValue&>(value).GetContent(); + + case ValueType_Utf8String: + return dynamic_cast<const Utf8StringValue&>(value).GetContent(); + + default: + //LOG(ERROR) << value.Format(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + template <typename T> + void IndexBackend::ReadListOfIntegers(std::list<T>& target, + DatabaseManager::CachedStatement& statement, + const Dictionary& args) + { + statement.Execute(args); + + target.clear(); + + if (!statement.IsDone()) + { + if (statement.GetResultFieldsCount() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + statement.SetResultFieldType(0, ValueType_Integer64); + + while (!statement.IsDone()) + { + target.push_back(static_cast<T>(ReadInteger64(statement, 0))); + statement.Next(); + } + } + } + + + void IndexBackend::ReadListOfStrings(std::list<std::string>& target, + DatabaseManager::CachedStatement& statement, + const Dictionary& args) + { + statement.Execute(args); + + target.clear(); + + if (!statement.IsDone()) + { + if (statement.GetResultFieldsCount() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + while (!statement.IsDone()) + { + target.push_back(ReadString(statement, 0)); + statement.Next(); + } + } + } + + + void IndexBackend::ReadChangesInternal(bool& done, + DatabaseManager::CachedStatement& statement, + const Dictionary& args, + uint32_t maxResults) + { + statement.Execute(args); + + uint32_t count = 0; + + while (count < maxResults && + !statement.IsDone()) + { + GetOutput().AnswerChange( + ReadInteger64(statement, 0), + ReadInteger32(statement, 1), + static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 3)), + GetPublicId(ReadInteger64(statement, 2)), + ReadString(statement, 4)); + + statement.Next(); + count++; + } + + done = (count < maxResults || + statement.IsDone()); + } + + + void IndexBackend::ReadExportedResourcesInternal(bool& done, + DatabaseManager::CachedStatement& statement, + const Dictionary& args, + uint32_t maxResults) + { + statement.Execute(args); + + uint32_t count = 0; + + while (count < maxResults && + !statement.IsDone()) + { + int64_t seq = ReadInteger64(statement, 0); + OrthancPluginResourceType resourceType = + static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 1)); + std::string publicId = ReadString(statement, 2); + + GetOutput().AnswerExportedResource(seq, + resourceType, + publicId, + ReadString(statement, 3), // modality + ReadString(statement, 8), // date + ReadString(statement, 4), // patient ID + ReadString(statement, 5), // study instance UID + ReadString(statement, 6), // series instance UID + ReadString(statement, 7)); // sop instance UID + + statement.Next(); + count++; + } + + done = (count < maxResults || + statement.IsDone()); + } + + + void IndexBackend::ClearDeletedFiles() + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM DeletedFiles"); + + statement.Execute(); + } + + + void IndexBackend::ClearDeletedResources() + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM DeletedResources"); + + statement.Execute(); + } + + + void IndexBackend::SignalDeletedFiles() + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT * FROM DeletedFiles"); + + statement.SetReadOnly(true); + statement.Execute(); + + while (!statement.IsDone()) + { + std::string a = ReadString(statement, 0); + std::string b = ReadString(statement, 5); + std::string c = ReadString(statement, 6); + + GetOutput().SignalDeletedAttachment(a.c_str(), + ReadInteger32(statement, 1), + ReadInteger64(statement, 3), + b.c_str(), + ReadInteger32(statement, 4), + ReadInteger64(statement, 2), + c.c_str()); + + statement.Next(); + } + } + + + void IndexBackend::SignalDeletedResources() + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT * FROM DeletedResources"); + + statement.SetReadOnly(true); + statement.Execute(); + + while (!statement.IsDone()) + { + GetOutput().SignalDeletedResource( + ReadString(statement, 1), + static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 0))); + + statement.Next(); + } + } + + + IndexBackend::IndexBackend(IDatabaseFactory* factory) : + manager_(factory) + { + } + + + void IndexBackend::AddAttachment(int64_t id, + const OrthancPluginAttachment& attachment) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "INSERT INTO AttachedFiles VALUES(${id}, ${type}, ${uuid}, " + "${compressed}, ${uncompressed}, ${compression}, ${hash}, ${hash-compressed})"); + + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("type", ValueType_Integer64); + statement.SetParameterType("uuid", ValueType_Utf8String); + statement.SetParameterType("compressed", ValueType_Integer64); + statement.SetParameterType("uncompressed", ValueType_Integer64); + statement.SetParameterType("compression", ValueType_Integer64); + statement.SetParameterType("hash", ValueType_Utf8String); + statement.SetParameterType("hash-compressed", ValueType_Utf8String); + + Dictionary args; + 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); + + statement.Execute(args); + } + + + void IndexBackend::AttachChild(int64_t parent, + int64_t child) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "UPDATE Resources SET parentId = ${parent} WHERE internalId = ${child}"); + + statement.SetParameterType("parent", ValueType_Integer64); + statement.SetParameterType("child", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("parent", parent); + args.SetIntegerValue("child", child); + + statement.Execute(args); + } + + + void IndexBackend::ClearChanges() + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM Changes"); + + statement.Execute(); + } + + + void IndexBackend::ClearExportedResources() + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM ExportedResources"); + + statement.Execute(); + } + + + void IndexBackend::DeleteAttachment(int64_t id, + int32_t attachment) + { + ClearDeletedFiles(); + + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM AttachedFiles WHERE id=${id} AND fileType=${type}"); + + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("type", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + args.SetIntegerValue("type", static_cast<int>(attachment)); + + statement.Execute(args); + } + + SignalDeletedFiles(); + } + + + void IndexBackend::DeleteMetadata(int64_t id, + int32_t metadataType) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM Metadata WHERE id=${id} and type=${type}"); + + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("type", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + args.SetIntegerValue("type", static_cast<int>(metadataType)); + + statement.Execute(args); + } + + + void IndexBackend::DeleteResource(int64_t id) + { + assert(GetDialect() != Dialect_MySQL); + + ClearDeletedFiles(); + ClearDeletedResources(); + + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, GetManager(), + "DELETE FROM RemainingAncestor"); + + statement.Execute(); + } + + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, GetManager(), + "DELETE FROM Resources WHERE internalId=${id}"); + + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + statement.Execute(args); + } + + + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT * FROM RemainingAncestor"); + + statement.Execute(); + + if (!statement.IsDone()) + { + GetOutput().SignalRemainingAncestor( + ReadString(statement, 1), + static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 0))); + + // There is at most 1 remaining ancestor + assert((statement.Next(), statement.IsDone())); + } + } + + SignalDeletedFiles(); + SignalDeletedResources(); + } + + + void IndexBackend::GetAllInternalIds(std::list<int64_t>& target, + OrthancPluginResourceType resourceType) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT internalId FROM Resources WHERE resourceType=${type}"); + + statement.SetReadOnly(true); + statement.SetParameterType("type", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("type", static_cast<int>(resourceType)); + + ReadListOfIntegers<int64_t>(target, statement, args); + } + + + void IndexBackend::GetAllPublicIds(std::list<std::string>& target, + OrthancPluginResourceType resourceType) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT publicId FROM Resources WHERE resourceType=${type}"); + + statement.SetReadOnly(true); + statement.SetParameterType("type", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("type", static_cast<int>(resourceType)); + + ReadListOfStrings(target, statement, args); + } + + + void IndexBackend::GetAllPublicIds(std::list<std::string>& target, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT publicId FROM (SELECT publicId FROM Resources " + "WHERE resourceType=${type}) AS tmp " + "ORDER BY tmp.publicId LIMIT ${limit} OFFSET ${since}"); + + statement.SetReadOnly(true); + statement.SetParameterType("type", ValueType_Integer64); + statement.SetParameterType("limit", ValueType_Integer64); + statement.SetParameterType("since", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("type", static_cast<int>(resourceType)); + args.SetIntegerValue("limit", limit); + args.SetIntegerValue("since", since); + + ReadListOfStrings(target, statement, args); + } + + + /* Use GetOutput().AnswerChange() */ + void IndexBackend::GetChanges(bool& done /*out*/, + int64_t since, + uint32_t maxResults) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT * FROM Changes WHERE seq>${since} ORDER BY seq LIMIT ${limit}"); + + statement.SetReadOnly(true); + statement.SetParameterType("limit", ValueType_Integer64); + statement.SetParameterType("since", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("limit", maxResults + 1); + args.SetIntegerValue("since", since); + + ReadChangesInternal(done, statement, args, maxResults); + } + + + void IndexBackend::GetChildrenInternalId(std::list<int64_t>& target /*out*/, + int64_t id) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT a.internalId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + ReadListOfIntegers<int64_t>(target, statement, args); + } + + + void IndexBackend::GetChildrenPublicId(std::list<std::string>& target /*out*/, + int64_t id) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT a.publicId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + ReadListOfStrings(target, statement, args); + } + + + /* Use GetOutput().AnswerExportedResource() */ + void IndexBackend::GetExportedResources(bool& done /*out*/, + int64_t since, + uint32_t maxResults) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT * FROM ExportedResources WHERE seq>${since} ORDER BY seq LIMIT ${limit}"); + + statement.SetReadOnly(true); + statement.SetParameterType("limit", ValueType_Integer64); + statement.SetParameterType("since", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("limit", maxResults + 1); + args.SetIntegerValue("since", since); + + ReadExportedResourcesInternal(done, statement, args, maxResults); + } + + + /* Use GetOutput().AnswerChange() */ + void IndexBackend::GetLastChange() + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); + + statement.SetReadOnly(true); + + Dictionary args; + + bool done; // Ignored + ReadChangesInternal(done, statement, args, 1); + } + + + /* Use GetOutput().AnswerExportedResource() */ + void IndexBackend::GetLastExportedResource() + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); + + statement.SetReadOnly(true); + + Dictionary args; + + bool done; // Ignored + ReadExportedResourcesInternal(done, statement, args, 1); + } + + + /* Use GetOutput().AnswerDicomTag() */ + void IndexBackend::GetMainDicomTags(int64_t id) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT * FROM MainDicomTags WHERE id=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + statement.Execute(args); + + while (!statement.IsDone()) + { + GetOutput().AnswerDicomTag(static_cast<uint16_t>(ReadInteger64(statement, 1)), + static_cast<uint16_t>(ReadInteger64(statement, 2)), + ReadString(statement, 3)); + statement.Next(); + } + } + + + std::string IndexBackend::GetPublicId(int64_t resourceId) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT publicId FROM Resources WHERE internalId=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", resourceId); + + statement.Execute(args); + + if (statement.IsDone()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + else + { + return ReadString(statement, 0); + } + } + + + uint64_t IndexBackend::GetResourceCount(OrthancPluginResourceType resourceType) + { + std::auto_ptr<DatabaseManager::CachedStatement> statement; + + switch (GetDialect()) + { + case Dialect_MySQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COUNT(*) AS UNSIGNED INT) FROM Resources WHERE resourceType=${type}")); + break; + + case Dialect_PostgreSQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COUNT(*) AS BIGINT) FROM Resources WHERE resourceType=${type}")); + break; + + case Dialect_SQLite: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT COUNT(*) FROM Resources WHERE resourceType=${type}")); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + statement->SetReadOnly(true); + statement->SetParameterType("type", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("type", resourceType); + + statement->Execute(args); + + return static_cast<uint64_t>(ReadInteger64(*statement, 0)); + } + + + OrthancPluginResourceType IndexBackend::GetResourceType(int64_t resourceId) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT resourceType FROM Resources WHERE internalId=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", resourceId); + + statement.Execute(args); + + if (statement.IsDone()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + else + { + return static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 0)); + } + } + + + uint64_t IndexBackend::GetTotalCompressedSize() + { + std::auto_ptr<DatabaseManager::CachedStatement> statement; + + // NB: "COALESCE" is used to replace "NULL" by "0" if the number of rows is empty + + switch (GetDialect()) + { + case Dialect_MySQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COALESCE(SUM(compressedSize), 0) AS UNSIGNED INTEGER) FROM AttachedFiles")); + break; + + case Dialect_PostgreSQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COALESCE(SUM(compressedSize), 0) AS BIGINT) FROM AttachedFiles")); + break; + + case Dialect_SQLite: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT COALESCE(SUM(compressedSize), 0) FROM AttachedFiles")); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + statement->SetReadOnly(true); + statement->Execute(); + + return static_cast<uint64_t>(ReadInteger64(*statement, 0)); + } + + + uint64_t IndexBackend::GetTotalUncompressedSize() + { + std::auto_ptr<DatabaseManager::CachedStatement> statement; + + // NB: "COALESCE" is used to replace "NULL" by "0" if the number of rows is empty + + switch (GetDialect()) + { + case Dialect_MySQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COALESCE(SUM(uncompressedSize), 0) AS UNSIGNED INTEGER) FROM AttachedFiles")); + break; + + case Dialect_PostgreSQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COALESCE(SUM(uncompressedSize), 0) AS BIGINT) FROM AttachedFiles")); + break; + + case Dialect_SQLite: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT COALESCE(SUM(uncompressedSize), 0) FROM AttachedFiles")); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + statement->SetReadOnly(true); + statement->Execute(); + + return static_cast<uint64_t>(ReadInteger64(*statement, 0)); + } + + + bool IndexBackend::IsExistingResource(int64_t internalId) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT * FROM Resources WHERE internalId=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", internalId); + + statement.Execute(args); + + return !statement.IsDone(); + } + + + bool IndexBackend::IsProtectedPatient(int64_t internalId) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT * FROM PatientRecyclingOrder WHERE patientId = ${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", internalId); + + statement.Execute(args); + + return statement.IsDone(); + } + + + void IndexBackend::ListAvailableMetadata(std::list<int32_t>& target /*out*/, + int64_t id) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT type FROM Metadata WHERE id=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + ReadListOfIntegers<int32_t>(target, statement, args); + } + + + void IndexBackend::ListAvailableAttachments(std::list<int32_t>& target /*out*/, + int64_t id) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT fileType FROM AttachedFiles WHERE id=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + ReadListOfIntegers<int32_t>(target, statement, args); + } + + + void IndexBackend::LogChange(const OrthancPluginChange& change) + { + int64_t id; + OrthancPluginResourceType type; + if (!LookupResource(id, type, change.publicId) || + type != change.resourceType) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); + } + + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "INSERT INTO Changes VALUES(${}, ${changeType}, ${id}, ${resourceType}, ${date})"); + + statement.SetParameterType("changeType", ValueType_Integer64); + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("resourceType", ValueType_Integer64); + statement.SetParameterType("date", ValueType_Utf8String); + + Dictionary args; + args.SetIntegerValue("changeType", change.changeType); + args.SetIntegerValue("id", id); + args.SetIntegerValue("resourceType", change.resourceType); + args.SetUtf8Value("date", change.date); + + statement.Execute(args); + } + + + void IndexBackend::LogExportedResource(const OrthancPluginExportedResource& resource) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "INSERT INTO ExportedResources VALUES(${}, ${type}, ${publicId}, " + "${modality}, ${patient}, ${study}, ${series}, ${instance}, ${date})"); + + statement.SetParameterType("type", ValueType_Integer64); + statement.SetParameterType("publicId", ValueType_Utf8String); + statement.SetParameterType("modality", ValueType_Utf8String); + statement.SetParameterType("patient", ValueType_Utf8String); + statement.SetParameterType("study", ValueType_Utf8String); + statement.SetParameterType("series", ValueType_Utf8String); + statement.SetParameterType("instance", ValueType_Utf8String); + statement.SetParameterType("date", ValueType_Utf8String); + + Dictionary args; + args.SetIntegerValue("type", resource.resourceType); + args.SetUtf8Value("publicId", resource.publicId); + args.SetUtf8Value("modality", resource.modality); + args.SetUtf8Value("patient", resource.patientId); + args.SetUtf8Value("study", resource.studyInstanceUid); + args.SetUtf8Value("series", resource.seriesInstanceUid); + args.SetUtf8Value("instance", resource.sopInstanceUid); + args.SetUtf8Value("date", resource.date); + + statement.Execute(args); + } + + + /* Use GetOutput().AnswerAttachment() */ + bool IndexBackend::LookupAttachment(int64_t id, + int32_t contentType) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT uuid, uncompressedSize, compressionType, compressedSize, " + "uncompressedHash, compressedHash FROM AttachedFiles WHERE id=${id} AND fileType=${type}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("type", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + args.SetIntegerValue("type", static_cast<int>(contentType)); + + statement.Execute(args); + + if (statement.IsDone()) + { + return false; + } + else + { + GetOutput().AnswerAttachment(ReadString(statement, 0), + contentType, + ReadInteger64(statement, 1), + ReadString(statement, 4), + ReadInteger32(statement, 2), + ReadInteger64(statement, 3), + ReadString(statement, 5)); + return true; + } + } + + + bool IndexBackend::LookupGlobalProperty(std::string& target /*out*/, + int32_t property) + { + return ::OrthancDatabases::LookupGlobalProperty(target, manager_, static_cast<Orthanc::GlobalProperty>(property)); + } + + + void IndexBackend::LookupIdentifier(std::list<int64_t>& target /*out*/, + OrthancPluginResourceType resourceType, + uint16_t group, + uint16_t element, + OrthancPluginIdentifierConstraint constraint, + const char* value) + { + std::auto_ptr<DatabaseManager::CachedStatement> statement; + + std::string header = + "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " + "d.id = r.internalId AND r.resourceType=${type} AND d.tagGroup=${group} " + "AND d.tagElement=${element} AND "; + + switch (constraint) + { + case OrthancPluginIdentifierConstraint_Equal: + header += "d.value = ${value}"; + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager_, header.c_str())); + break; + + case OrthancPluginIdentifierConstraint_SmallerOrEqual: + header += "d.value <= ${value}"; + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager_, header.c_str())); + break; + + case OrthancPluginIdentifierConstraint_GreaterOrEqual: + header += "d.value >= ${value}"; + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager_, header.c_str())); + break; + + case OrthancPluginIdentifierConstraint_Wildcard: + header += "d.value LIKE ${value}"; + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager_, header.c_str())); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); + } + + statement->SetReadOnly(true); + statement->SetParameterType("type", ValueType_Integer64); + statement->SetParameterType("group", ValueType_Integer64); + statement->SetParameterType("element", ValueType_Integer64); + statement->SetParameterType("value", ValueType_Utf8String); + + Dictionary args; + args.SetIntegerValue("type", resourceType); + args.SetIntegerValue("group", group); + args.SetIntegerValue("element", element); + + if (constraint == OrthancPluginIdentifierConstraint_Wildcard) + { + args.SetUtf8Value("value", ConvertWildcardToLike(value)); + } + else + { + args.SetUtf8Value("value", value); + } + + statement->Execute(args); + + target.clear(); + while (!statement->IsDone()) + { + target.push_back(ReadInteger64(*statement, 0)); + statement->Next(); + } + } + + + void IndexBackend::LookupIdentifierRange(std::list<int64_t>& target /*out*/, + OrthancPluginResourceType resourceType, + uint16_t group, + uint16_t element, + const char* start, + const char* end) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " + "d.id = r.internalId AND r.resourceType=${type} AND d.tagGroup=${group} " + "AND d.tagElement=${element} AND d.value>=${start} AND d.value<=${end}"); + + statement.SetReadOnly(true); + statement.SetParameterType("type", ValueType_Integer64); + statement.SetParameterType("group", ValueType_Integer64); + statement.SetParameterType("element", ValueType_Integer64); + statement.SetParameterType("start", ValueType_Utf8String); + statement.SetParameterType("end", ValueType_Utf8String); + + Dictionary args; + args.SetIntegerValue("type", resourceType); + args.SetIntegerValue("group", group); + args.SetIntegerValue("element", element); + args.SetUtf8Value("start", start); + args.SetUtf8Value("end", end); + + statement.Execute(args); + + target.clear(); + while (!statement.IsDone()) + { + target.push_back(ReadInteger64(statement, 0)); + statement.Next(); + } + } + + + bool IndexBackend::LookupMetadata(std::string& target /*out*/, + int64_t id, + int32_t metadataType) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT value FROM Metadata WHERE id=${id} and type=${type}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("type", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + args.SetIntegerValue("type", metadataType); + + statement.Execute(args); + + if (statement.IsDone()) + { + return false; + } + else + { + target = ReadString(statement, 0); + return true; + } + } + + + bool IndexBackend::LookupParent(int64_t& parentId /*out*/, + int64_t resourceId) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT parentId FROM Resources WHERE internalId=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", resourceId); + + statement.Execute(args); + + if (statement.IsDone() || + statement.GetResultField(0).GetType() == ValueType_Null) + { + return false; + } + else + { + parentId = ReadInteger64(statement, 0); + return true; + } + } + + + bool IndexBackend::LookupResource(int64_t& id /*out*/, + OrthancPluginResourceType& type /*out*/, + const char* publicId) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT internalId, resourceType FROM Resources WHERE publicId=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Utf8String); + + Dictionary args; + args.SetUtf8Value("id", publicId); + + statement.Execute(args); + + if (statement.IsDone()) + { + return false; + } + else + { + id = ReadInteger64(statement, 0); + type = static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 1)); + return true; + } + } + + + bool IndexBackend::SelectPatientToRecycle(int64_t& internalId /*out*/) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1"); + + statement.SetReadOnly(true); + statement.Execute(); + + if (statement.IsDone()) + { + return false; + } + else + { + internalId = ReadInteger64(statement, 0); + return true; + } + } + + + bool IndexBackend::SelectPatientToRecycle(int64_t& internalId /*out*/, + int64_t patientIdToAvoid) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT patientId FROM PatientRecyclingOrder " + "WHERE patientId != ${id} ORDER BY seq ASC LIMIT 1"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", patientIdToAvoid); + + statement.Execute(args); + + if (statement.IsDone()) + { + return false; + } + else + { + internalId = ReadInteger64(statement, 0); + return true; + } + } + + + void IndexBackend::SetGlobalProperty(int32_t property, + const char* value) + { + return ::OrthancDatabases::SetGlobalProperty(manager_, static_cast<Orthanc::GlobalProperty>(property), value); + } + + + static void ExecuteSetTag(DatabaseManager::CachedStatement& statement, + int64_t id, + uint16_t group, + uint16_t element, + const char* value) + { + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("group", ValueType_Integer64); + statement.SetParameterType("element", ValueType_Integer64); + statement.SetParameterType("value", ValueType_Utf8String); + + Dictionary args; + args.SetIntegerValue("id", id); + args.SetIntegerValue("group", group); + args.SetIntegerValue("element", element); + args.SetUtf8Value("value", value); + + statement.Execute(args); + } + + + void IndexBackend::SetMainDicomTag(int64_t id, + uint16_t group, + uint16_t element, + const char* value) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "INSERT INTO MainDicomTags VALUES(${id}, ${group}, ${element}, ${value})"); + + ExecuteSetTag(statement, id, group, element, value); + } + + + void IndexBackend::SetIdentifierTag(int64_t id, + uint16_t group, + uint16_t element, + const char* value) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "INSERT INTO DicomIdentifiers VALUES(${id}, ${group}, ${element}, ${value})"); + + ExecuteSetTag(statement, id, group, element, value); + } + + + void IndexBackend::SetMetadata(int64_t id, + int32_t metadataType, + const char* value) + { + if (GetDialect() == Dialect_SQLite) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "INSERT OR REPLACE INTO Metadata VALUES (${id}, ${type}, ${value})"); + + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("type", ValueType_Integer64); + statement.SetParameterType("value", ValueType_Utf8String); + + Dictionary args; + args.SetIntegerValue("id", id); + args.SetIntegerValue("type", metadataType); + args.SetUtf8Value("value", value); + + statement.Execute(args); + } + else + { + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM Metadata WHERE id=${id} AND type=${type}"); + + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("type", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + args.SetIntegerValue("type", metadataType); + + statement.Execute(args); + } + + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "INSERT INTO Metadata VALUES (${id}, ${type}, ${value})"); + + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("type", ValueType_Integer64); + statement.SetParameterType("value", ValueType_Utf8String); + + Dictionary args; + args.SetIntegerValue("id", id); + args.SetIntegerValue("type", metadataType); + args.SetUtf8Value("value", value); + + statement.Execute(args); + } + } + } + + + void IndexBackend::SetProtectedPatient(int64_t internalId, + bool isProtected) + { + if (isProtected) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM PatientRecyclingOrder WHERE patientId=${id}"); + + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", internalId); + + statement.Execute(args); + } + else if (IsProtectedPatient(internalId)) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "INSERT INTO PatientRecyclingOrder VALUES(${}, ${id})"); + + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", internalId); + + statement.Execute(args); + } + else + { + // Nothing to do: The patient is already unprotected + } + } + + + uint32_t IndexBackend::GetDatabaseVersion() + { + std::string version = "unknown"; + + if (LookupGlobalProperty(version, Orthanc::GlobalProperty_DatabaseSchemaVersion)) + { + try + { + return boost::lexical_cast<unsigned int>(version); + } + catch (boost::bad_lexical_cast&) + { + } + } + + LOG(ERROR) << "The database is corrupted. Drop it manually for Orthanc to recreate it"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); + } + + + /** + * Upgrade the database to the specified version of the database + * schema. The upgrade script is allowed to make calls to + * OrthancPluginReconstructMainDicomTags(). + **/ + void IndexBackend::UpgradeDatabase(uint32_t targetVersion, + OrthancPluginStorageArea* storageArea) + { + LOG(ERROR) << "Upgrading database is not implemented by this plugin"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void IndexBackend::ClearMainDicomTags(int64_t internalId) + { + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM MainDicomTags WHERE id=${id}"); + + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", internalId); + + statement.Execute(args); + } + + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "DELETE FROM DicomIdentifiers WHERE id=${id}"); + + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", internalId); + + statement.Execute(args); + } + } + + + // For unit testing only! + uint64_t IndexBackend::GetResourcesCount() + { + std::auto_ptr<DatabaseManager::CachedStatement> statement; + + switch (GetDialect()) + { + case Dialect_MySQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COUNT(*) AS UNSIGNED INT) FROM Resources")); + break; + + case Dialect_PostgreSQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COUNT(*) AS BIGINT) FROM Resources")); + break; + + case Dialect_SQLite: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT COUNT(*) FROM Resources")); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + statement->SetReadOnly(true); + statement->Execute(); + + return static_cast<uint64_t>(ReadInteger64(*statement, 0)); + } + + + // For unit testing only! + uint64_t IndexBackend::GetUnprotectedPatientsCount() + { + std::auto_ptr<DatabaseManager::CachedStatement> statement; + + switch (GetDialect()) + { + case Dialect_MySQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COUNT(*) AS UNSIGNED INT) FROM PatientRecyclingOrder")); + break; + + case Dialect_PostgreSQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT CAST(COUNT(*) AS BIGINT) FROM PatientRecyclingOrder")); + break; + + case Dialect_SQLite: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT COUNT(*) FROM PatientRecyclingOrder")); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + statement->SetReadOnly(true); + statement->Execute(); + + return static_cast<uint64_t>(ReadInteger64(*statement, 0)); + } + + + // For unit testing only! + bool IndexBackend::GetParentPublicId(std::string& target, + int64_t id) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT a.publicId FROM Resources AS a, Resources AS b " + "WHERE a.internalId = b.parentId AND b.internalId = ${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + statement.Execute(args); + + if (statement.IsDone()) + { + return false; + } + else + { + target = ReadString(statement, 0); + return true; + } + } + + + // For unit tests only! + void IndexBackend::GetChildren(std::list<std::string>& childrenPublicIds, + int64_t id) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT publicId FROM Resources WHERE parentId=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + ReadListOfStrings(childrenPublicIds, statement, args); + } +}