Mercurial > hg > orthanc-postgresql
view IndexPlugin/PostgreSQLWrapper.cpp @ 153:6f90b762be6a
no need for patch.exe
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 04 Jan 2018 07:26:56 +0100 (2018-01-04) |
parents | 5611e6b1ec14 |
children | 25d524f26a14 |
line wrap: on
line source
/** * 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 "PostgreSQLWrapper.h" #include "EmbeddedResources.h" #include "../Core/Configuration.h" #include "../Core/PostgreSQLException.h" #include "../Core/PostgreSQLTransaction.h" #include <boost/lexical_cast.hpp> namespace OrthancPlugins { PostgreSQLWrapper::PostgreSQLWrapper(OrthancPluginContext* context, PostgreSQLConnection* connection, bool useLock, bool allowUnlock) : context_(context), connection_(connection), globalProperties_(*connection, useLock, GlobalProperty_IndexLock) { globalProperties_.Lock(allowUnlock); Prepare(); /** * Below are the PostgreSQL precompiled statements that are used * in more than 1 method of this class. **/ getPublicId_.reset (new PostgreSQLStatement (*connection_, "SELECT publicId FROM Resources WHERE internalId=$1")); getPublicId_->DeclareInputInteger64(0); clearDeletedFiles_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM DeletedFiles")); clearDeletedResources_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM DeletedResources")); } void PostgreSQLWrapper::SignalDeletedFilesAndResources() { if (getDeletedFiles_.get() == NULL || getDeletedResources_.get() == NULL) { getDeletedFiles_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM DeletedFiles")); getDeletedResources_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM DeletedResources")); } { PostgreSQLResult result(*getDeletedFiles_); while (!result.IsDone()) { GetOutput().SignalDeletedAttachment(result.GetString(0).c_str(), result.GetInteger(1), result.GetInteger64(3), result.GetString(5).c_str(), result.GetInteger(4), result.GetInteger64(2), result.GetString(6).c_str()); result.Step(); } } { PostgreSQLResult result(*getDeletedResources_); while (!result.IsDone()) { OrthancPluginResourceType type = static_cast<OrthancPluginResourceType>(result.GetInteger(0)); GetOutput().SignalDeletedResource(result.GetString(1), type); result.Step(); } } } void PostgreSQLWrapper::Prepare() { uint32_t expectedVersion; if (context_) { expectedVersion = OrthancPluginGetExpectedDatabaseVersion(context_); } else { // This case only occurs during unit testing expectedVersion = 6; } /* Check the expected version of the database */ if (expectedVersion != 5 && expectedVersion != 6) { char info[1024]; sprintf(info, "This database plugin is incompatible with your version of Orthanc " "expecting the DB schema version %d, but this plugin is compatible with versions 5 or 6", expectedVersion); OrthancPluginLogError(context_, info); throw PostgreSQLException(info); } PostgreSQLTransaction t(*connection_); if (!connection_->DoesTableExist("Resources")) { std::string query; if (expectedVersion == 5) { EmbeddedResources::GetFileResource(query, EmbeddedResources::POSTGRESQL_PREPARE_V5); } else { EmbeddedResources::GetFileResource(query, EmbeddedResources::POSTGRESQL_PREPARE_V6); } connection_->Execute(query); // Execute the common initialization between versions 5 and 6 EmbeddedResources::GetFileResource(query, EmbeddedResources::POSTGRESQL_PREPARE); connection_->Execute(query); // Set the version of the database schema // The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration connection_->Execute("INSERT INTO GlobalProperties VALUES (1, '" + boost::lexical_cast<std::string>(expectedVersion) + "')"); } // Check the version of the database version_ = GetDatabaseVersion(); if (version_ != 5 && version_ != 6) { std::string message = ("Incompatible version of the Orthanc PostgreSQL database: " + boost::lexical_cast<std::string>(version_)); throw PostgreSQLException(message); } t.Commit(); } PostgreSQLWrapper::~PostgreSQLWrapper() { globalProperties_.Unlock(); } void PostgreSQLWrapper::AddAttachment(int64_t id, const OrthancPluginAttachment& attachment) { if (attachFile_.get() == NULL) { attachFile_.reset (new PostgreSQLStatement (*connection_, "INSERT INTO AttachedFiles VALUES($1, $2, $3, $4, $5, $6, $7, $8)")); attachFile_->DeclareInputInteger64(0); attachFile_->DeclareInputInteger(1); attachFile_->DeclareInputString(2); attachFile_->DeclareInputInteger64(3); attachFile_->DeclareInputInteger64(4); attachFile_->DeclareInputInteger(5); attachFile_->DeclareInputString(6); attachFile_->DeclareInputString(7); } attachFile_->BindInteger64(0, id); attachFile_->BindInteger(1, attachment.contentType); attachFile_->BindString(2, attachment.uuid); attachFile_->BindInteger64(3, attachment.compressedSize); attachFile_->BindInteger64(4, attachment.uncompressedSize); attachFile_->BindInteger(5, attachment.compressionType); attachFile_->BindString(6, attachment.uncompressedHash); attachFile_->BindString(7, attachment.compressedHash); attachFile_->Run(); } void PostgreSQLWrapper::AttachChild(int64_t parent, int64_t child) { if (attachChild_.get() == NULL) { attachChild_.reset (new PostgreSQLStatement (*connection_, "UPDATE Resources SET parentId = $1 WHERE internalId = $2")); attachChild_->DeclareInputInteger64(0); attachChild_->DeclareInputInteger64(1); } attachChild_->BindInteger64(0, parent); attachChild_->BindInteger64(1, child); attachChild_->Run(); } void PostgreSQLWrapper::ClearTable(const std::string& tableName) { connection_->Execute("DELETE FROM " + tableName); } int64_t PostgreSQLWrapper::CreateResource(const char* publicId, OrthancPluginResourceType type) { if (createResource_.get() == NULL) { createResource_.reset (new PostgreSQLStatement (*connection_, "INSERT INTO Resources VALUES(DEFAULT, $1, $2, NULL) RETURNING internalId")); createResource_->DeclareInputInteger(0); createResource_->DeclareInputString(1); } createResource_->BindInteger(0, static_cast<int>(type)); createResource_->BindString(1, publicId); PostgreSQLResult result(*createResource_); if (result.IsDone()) { throw PostgreSQLException(); } return result.GetInteger64(0); } void PostgreSQLWrapper::DeleteAttachment(int64_t id, int32_t attachment) { clearDeletedFiles_->Run(); clearDeletedResources_->Run(); if (deleteAttachment_.get() == NULL) { deleteAttachment_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM AttachedFiles WHERE id=$1 AND fileType=$2")); deleteAttachment_->DeclareInputInteger64(0); deleteAttachment_->DeclareInputInteger(1); } deleteAttachment_->BindInteger64(0, id); deleteAttachment_->BindInteger(1, static_cast<int>(attachment)); deleteAttachment_->Run(); SignalDeletedFilesAndResources(); } void PostgreSQLWrapper::DeleteMetadata(int64_t id, int32_t type) { if (deleteMetadata_.get() == NULL) { deleteMetadata_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM Metadata WHERE id=$1 and type=$2")); deleteMetadata_->DeclareInputInteger64(0); deleteMetadata_->DeclareInputInteger(1); } deleteMetadata_->BindInteger64(0, id); deleteMetadata_->BindInteger(1, static_cast<int>(type)); deleteMetadata_->Run(); } void PostgreSQLWrapper::DeleteResource(int64_t id) { if (clearRemainingAncestor_.get() == NULL || getRemainingAncestor_.get() == NULL) { clearRemainingAncestor_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM RemainingAncestor")); getRemainingAncestor_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM RemainingAncestor")); } clearDeletedFiles_->Run(); clearDeletedResources_->Run(); clearRemainingAncestor_->Run(); if (deleteResource_.get() == NULL) { deleteResource_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM Resources WHERE internalId=$1")); deleteResource_->DeclareInputInteger64(0); } deleteResource_->BindInteger64(0, id); deleteResource_->Run(); PostgreSQLResult result(*getRemainingAncestor_); if (!result.IsDone()) { GetOutput().SignalRemainingAncestor(result.GetString(1), static_cast<OrthancPluginResourceType>(result.GetInteger(0))); // There is at most 1 remaining ancestor assert((result.Step(), result.IsDone())); } SignalDeletedFilesAndResources(); } void PostgreSQLWrapper::GetAllPublicIds(std::list<std::string>& target, OrthancPluginResourceType resourceType) { if (getAllPublicIds_.get() == NULL) { getAllPublicIds_.reset (new PostgreSQLStatement(*connection_, "SELECT publicId FROM Resources WHERE resourceType=$1")); getAllPublicIds_->DeclareInputInteger(0); } getAllPublicIds_->BindInteger(0, static_cast<int>(resourceType)); PostgreSQLResult result(*getAllPublicIds_); target.clear(); while (!result.IsDone()) { target.push_back(result.GetString(0)); result.Step(); } } void PostgreSQLWrapper::GetAllInternalIds(std::list<int64_t>& target, OrthancPluginResourceType resourceType) { if (getAllInternalIds_.get() == NULL) { getAllInternalIds_.reset (new PostgreSQLStatement(*connection_, "SELECT internalId FROM Resources WHERE resourceType=$1")); getAllInternalIds_->DeclareInputInteger(0); } getAllInternalIds_->BindInteger(0, static_cast<int>(resourceType)); PostgreSQLResult result(*getAllInternalIds_); target.clear(); while (!result.IsDone()) { target.push_back(result.GetInteger64(0)); result.Step(); } } void PostgreSQLWrapper::GetAllPublicIds(std::list<std::string>& target, OrthancPluginResourceType resourceType, uint64_t since, uint64_t limit) { if (getAllPublicIdsWithLimit_.get() == NULL) { getAllPublicIdsWithLimit_.reset (new PostgreSQLStatement(*connection_, "SELECT * FROM (SELECT publicId FROM Resources WHERE resourceType=$1) AS tmp ORDER BY tmp.publicId LIMIT $2 OFFSET $3")); getAllPublicIdsWithLimit_->DeclareInputInteger(0); getAllPublicIdsWithLimit_->DeclareInputInteger64(1); getAllPublicIdsWithLimit_->DeclareInputInteger64(2); } getAllPublicIdsWithLimit_->BindInteger(0, static_cast<int>(resourceType)); getAllPublicIdsWithLimit_->BindInteger64(1, limit); getAllPublicIdsWithLimit_->BindInteger64(2, since); PostgreSQLResult result(*getAllPublicIdsWithLimit_); target.clear(); while (!result.IsDone()) { target.push_back(result.GetString(0)); result.Step(); } } void PostgreSQLWrapper::GetChangesInternal(bool& done, PostgreSQLStatement& s, uint32_t maxResults) { PostgreSQLResult result(s); uint32_t count = 0; while (count < maxResults && !result.IsDone()) { GetOutput().AnswerChange(result.GetInteger64(0), result.GetInteger(1), static_cast<OrthancPluginResourceType>(result.GetInteger(3)), GetPublicId(result.GetInteger64(2)), result.GetString(4)); result.Step(); count++; } done = !(count == maxResults && !result.IsDone()); } void PostgreSQLWrapper::GetChanges(bool& done, int64_t since, uint32_t maxResults) { if (getChanges_.get() == NULL) { getChanges_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM Changes WHERE seq>$1 ORDER BY seq LIMIT $2")); getChanges_->DeclareInputInteger64(0); getChanges_->DeclareInputInteger(1); } getChanges_->BindInteger64(0, since); getChanges_->BindInteger(1, maxResults + 1); GetChangesInternal(done, *getChanges_, maxResults); } void PostgreSQLWrapper::GetLastChange() { if (getLastChange_.get() == NULL) { getLastChange_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1")); } bool done; // Ignored GetChangesInternal(done, *getLastChange_, 1); } void PostgreSQLWrapper::GetChildrenInternalId(std::list<int64_t>& target, int64_t id) { if (getChildrenInternalId_.get() == NULL) { getChildrenInternalId_.reset (new PostgreSQLStatement(*connection_, "SELECT a.internalId FROM Resources AS a, Resources AS b " "WHERE a.parentId = b.internalId AND b.internalId = $1")); getChildrenInternalId_->DeclareInputInteger64(0); } getChildrenInternalId_->BindInteger64(0, id); PostgreSQLResult result(*getChildrenInternalId_); target.clear(); while (!result.IsDone()) { target.push_back(result.GetInteger64(0)); result.Step(); } } void PostgreSQLWrapper::GetChildrenPublicId(std::list<std::string>& target, int64_t id) { if (getChildrenPublicId_.get() == NULL) { getChildrenPublicId_.reset (new PostgreSQLStatement(*connection_, "SELECT a.publicId FROM Resources AS a, Resources AS b " "WHERE a.parentId = b.internalId AND b.internalId = $1")); getChildrenPublicId_->DeclareInputInteger64(0); } getChildrenPublicId_->BindInteger64(0, id); PostgreSQLResult result(*getChildrenPublicId_); target.clear(); while (!result.IsDone()) { target.push_back(result.GetString(0)); result.Step(); } } void PostgreSQLWrapper::GetExportedResourcesInternal(bool& done, PostgreSQLStatement& s, uint32_t maxResults) { PostgreSQLResult result(s); uint32_t count = 0; while (count < maxResults && !result.IsDone()) { int64_t seq = result.GetInteger64(0); OrthancPluginResourceType resourceType = static_cast<OrthancPluginResourceType>(result.GetInteger(1)); std::string publicId = result.GetString(2); GetOutput().AnswerExportedResource(seq, resourceType, publicId, result.GetString(3), // modality result.GetString(8), // date result.GetString(4), // patient ID result.GetString(5), // study instance UID result.GetString(6), // series instance UID result.GetString(7)); // sop instance UID result.Step(); count++; } done = !(count == maxResults && !result.IsDone()); } void PostgreSQLWrapper::GetExportedResources(bool& done, int64_t since, uint32_t maxResults) { if (getExports_.get() == NULL) { getExports_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM ExportedResources WHERE seq>$1 ORDER BY seq LIMIT $2")); getExports_->DeclareInputInteger64(0); getExports_->DeclareInputInteger(1); } getExports_->BindInteger64(0, since); getExports_->BindInteger(1, maxResults + 1); GetExportedResourcesInternal(done, *getExports_, maxResults); } void PostgreSQLWrapper::GetLastExportedResource() { if (getLastExport_.get() == NULL) { getLastExport_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1")); } bool done; // Ignored GetExportedResourcesInternal(done, *getLastExport_, 1); } void PostgreSQLWrapper::GetMainDicomTags(int64_t id) { if (getMainDicomTags1_.get() == NULL || getMainDicomTags2_.get() == NULL) { getMainDicomTags1_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM MainDicomTags WHERE id=$1")); getMainDicomTags1_->DeclareInputInteger64(0); getMainDicomTags2_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM DicomIdentifiers WHERE id=$1")); getMainDicomTags2_->DeclareInputInteger64(0); } { getMainDicomTags1_->BindInteger64(0, id); PostgreSQLResult result(*getMainDicomTags1_); while (!result.IsDone()) { GetOutput().AnswerDicomTag(static_cast<uint16_t>(result.GetInteger(1)), static_cast<uint16_t>(result.GetInteger(2)), result.GetString(3)); result.Step(); } } if (version_ == 5) { getMainDicomTags2_->BindInteger64(0, id); PostgreSQLResult result(*getMainDicomTags2_); while (!result.IsDone()) { GetOutput().AnswerDicomTag(static_cast<uint16_t>(result.GetInteger(1)), static_cast<uint16_t>(result.GetInteger(2)), result.GetString(3)); result.Step(); } } } std::string PostgreSQLWrapper::GetPublicId(int64_t resourceId) { getPublicId_->BindInteger64(0, resourceId); PostgreSQLResult result(*getPublicId_); if (result.IsDone()) { throw PostgreSQLException("Unknown resource"); } return result.GetString(0); } uint64_t PostgreSQLWrapper::GetResourceCount(OrthancPluginResourceType resourceType) { if (getResourceCount_.get() == NULL) { getResourceCount_.reset (new PostgreSQLStatement (*connection_, "SELECT CAST(COUNT(*) AS BIGINT) FROM Resources WHERE resourceType=$1")); getResourceCount_->DeclareInputInteger(0); } getResourceCount_->BindInteger(0, static_cast<int>(resourceType)); PostgreSQLResult result(*getResourceCount_); if (result.IsDone()) { throw PostgreSQLException(); } if (result.IsNull(0)) { return 0; } else { return result.GetInteger64(0); } } OrthancPluginResourceType PostgreSQLWrapper::GetResourceType(int64_t resourceId) { if (getResourceType_.get() == NULL) { getResourceType_.reset (new PostgreSQLStatement (*connection_, "SELECT resourceType FROM Resources WHERE internalId=$1")); getResourceType_->DeclareInputInteger64(0); } getResourceType_->BindInteger64(0, resourceId); PostgreSQLResult result(*getResourceType_); if (result.IsDone()) { throw PostgreSQLException("Unknown resource"); } return static_cast<OrthancPluginResourceType>(result.GetInteger(0)); } uint64_t PostgreSQLWrapper::GetTotalCompressedSize() { if (getTotalCompressedSize_.get() == NULL) { getTotalCompressedSize_.reset (new PostgreSQLStatement (*connection_, "SELECT CAST(SUM(compressedSize) AS BIGINT) FROM AttachedFiles")); } PostgreSQLResult result(*getTotalCompressedSize_); if (result.IsDone()) { throw PostgreSQLException(); } if (result.IsNull(0)) { return 0; } else { return result.GetInteger64(0); } } uint64_t PostgreSQLWrapper::GetTotalUncompressedSize() { if (getTotalUncompressedSize_.get() == NULL) { getTotalUncompressedSize_.reset (new PostgreSQLStatement (*connection_, "SELECT CAST(SUM(uncompressedSize) AS BIGINT) FROM AttachedFiles")); } PostgreSQLResult result(*getTotalUncompressedSize_); if (result.IsDone()) { throw PostgreSQLException(); } if (result.IsNull(0)) { return 0; } else { return result.GetInteger64(0); } } bool PostgreSQLWrapper::IsExistingResource(int64_t internalId) { getPublicId_->BindInteger64(0, internalId); PostgreSQLResult result(*getPublicId_); return !result.IsDone(); } bool PostgreSQLWrapper::IsProtectedPatient(int64_t internalId) { if (isProtectedPatient_.get() == NULL) { isProtectedPatient_.reset (new PostgreSQLStatement (*connection_, "SELECT * FROM PatientRecyclingOrder WHERE patientId = $1")); isProtectedPatient_->DeclareInputInteger64(0); } isProtectedPatient_->BindInteger64(0, internalId); PostgreSQLResult result(*isProtectedPatient_); return result.IsDone(); } void PostgreSQLWrapper::ListAvailableMetadata(std::list<int32_t>& target, int64_t id) { if (listMetadata_.get() == NULL) { listMetadata_.reset (new PostgreSQLStatement (*connection_, "SELECT type FROM Metadata WHERE id=$1")); listMetadata_->DeclareInputInteger64(0); } listMetadata_->BindInteger64(0, id); PostgreSQLResult result(*listMetadata_); target.clear(); while (!result.IsDone()) { target.push_back(static_cast<int32_t>(result.GetInteger(0))); result.Step(); } } void PostgreSQLWrapper::ListAvailableAttachments(std::list<int32_t>& target, int64_t id) { if (listAttachments_.get() == NULL) { listAttachments_.reset (new PostgreSQLStatement (*connection_, "SELECT fileType FROM AttachedFiles WHERE id=$1")); listAttachments_->DeclareInputInteger64(0); } listAttachments_->BindInteger64(0, id); PostgreSQLResult result(*listAttachments_); target.clear(); while (!result.IsDone()) { target.push_back(static_cast<int32_t>(result.GetInteger(0))); result.Step(); } } void PostgreSQLWrapper::LogChange(const OrthancPluginChange& change) { if (logChange_.get() == NULL) { logChange_.reset (new PostgreSQLStatement (*connection_, "INSERT INTO Changes VALUES(DEFAULT, $1, $2, $3, $4)")); logChange_->DeclareInputInteger(0); logChange_->DeclareInputInteger64(1); logChange_->DeclareInputInteger(2); logChange_->DeclareInputString(3); } int64_t id; OrthancPluginResourceType type; if (!LookupResource(id, type, change.publicId) || type != change.resourceType) { throw PostgreSQLException(); } logChange_->BindInteger(0, change.changeType); logChange_->BindInteger64(1, id); logChange_->BindInteger(2, change.resourceType); logChange_->BindString(3, change.date); logChange_->Run(); } void PostgreSQLWrapper::LogExportedResource(const OrthancPluginExportedResource& resource) { if (logExport_.get() == NULL) { logExport_.reset (new PostgreSQLStatement (*connection_, "INSERT INTO ExportedResources VALUES(DEFAULT, $1, $2, $3, $4, $5, $6, $7, $8)")); logExport_->DeclareInputInteger(0); logExport_->DeclareInputString(1); logExport_->DeclareInputString(2); logExport_->DeclareInputString(3); logExport_->DeclareInputString(4); logExport_->DeclareInputString(5); logExport_->DeclareInputString(6); logExport_->DeclareInputString(7); } logExport_->BindInteger(0, resource.resourceType); logExport_->BindString(1, resource.publicId); logExport_->BindString(2, resource.modality); logExport_->BindString(3, resource.patientId); logExport_->BindString(4, resource.studyInstanceUid); logExport_->BindString(5, resource.seriesInstanceUid); logExport_->BindString(6, resource.sopInstanceUid); logExport_->BindString(7, resource.date); logExport_->Run(); } bool PostgreSQLWrapper::LookupAttachment(int64_t id, int32_t contentType) { if (lookupAttachment_.get() == NULL) { lookupAttachment_.reset (new PostgreSQLStatement (*connection_, "SELECT uuid, uncompressedSize, compressionType, compressedSize, " "uncompressedHash, compressedHash FROM AttachedFiles WHERE id=$1 AND fileType=$2")); lookupAttachment_->DeclareInputInteger64(0); lookupAttachment_->DeclareInputInteger(1); } lookupAttachment_->BindInteger64(0, id); lookupAttachment_->BindInteger(1, static_cast<int>(contentType)); PostgreSQLResult result(*lookupAttachment_); if (!result.IsDone()) { GetOutput().AnswerAttachment(result.GetString(0), contentType, result.GetInteger64(1), result.GetString(4), result.GetInteger(2), result.GetInteger64(3), result.GetString(5)); return true; } else { return false; } } // Used only if Orthanc <= 0.9.4 void PostgreSQLWrapper::LookupIdentifier(std::list<int64_t>& target, uint16_t group, uint16_t element, const char* value) { if (lookupIdentifier1_.get() == NULL) { lookupIdentifier1_.reset (new PostgreSQLStatement (*connection_, "SELECT id FROM DicomIdentifiers WHERE tagGroup=$1 AND tagElement=$2 and value=$3")); lookupIdentifier1_->DeclareInputInteger(0); lookupIdentifier1_->DeclareInputInteger(1); if (version_ == 5) { lookupIdentifier1_->DeclareInputBinary(2); } else { lookupIdentifier1_->DeclareInputString(2); } } lookupIdentifier1_->BindInteger(0, group); lookupIdentifier1_->BindInteger(1, element); lookupIdentifier1_->BindString(2, value); PostgreSQLResult result(*lookupIdentifier1_); target.clear(); while (!result.IsDone()) { target.push_back(result.GetInteger64(0)); result.Step(); } } // Used only if Orthanc <= 0.9.4 void PostgreSQLWrapper::LookupIdentifier(std::list<int64_t>& target, const char* value) { if (lookupIdentifier2_.get() == NULL) { lookupIdentifier2_.reset (new PostgreSQLStatement (*connection_, "SELECT id FROM DicomIdentifiers WHERE value=$1")); if (version_ == 5) { lookupIdentifier2_->DeclareInputBinary(0); } else { lookupIdentifier2_->DeclareInputString(0); } } lookupIdentifier2_->BindString(0, value); PostgreSQLResult result(*lookupIdentifier2_); target.clear(); while (!result.IsDone()) { target.push_back(result.GetInteger64(0)); result.Step(); } } #if !(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER <= 0 && ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER <= 9 && ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER <= 4) 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; } // Used only if Orthanc >= 0.9.5 void PostgreSQLWrapper::LookupIdentifier(std::list<int64_t>& target, OrthancPluginResourceType level, uint16_t group, uint16_t element, OrthancPluginIdentifierConstraint constraint, const char* value) { if (lookupIdentifierEQ_.get() == NULL) { lookupIdentifierEQ_.reset (new PostgreSQLStatement (*connection_, "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " "d.id = r.internalId AND r.resourceType=$1 AND d.tagGroup=$2 AND d.tagElement=$3 AND d.value=$4")); lookupIdentifierEQ_->DeclareInputInteger(0); lookupIdentifierEQ_->DeclareInputInteger(1); lookupIdentifierEQ_->DeclareInputInteger(2); lookupIdentifierEQ_->DeclareInputString(3); } if (lookupIdentifierLE_.get() == NULL) { lookupIdentifierLE_.reset (new PostgreSQLStatement (*connection_, "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " "d.id = r.internalId AND r.resourceType=$1 AND d.tagGroup=$2 AND d.tagElement=$3 AND d.value<=$4")); lookupIdentifierLE_->DeclareInputInteger(0); lookupIdentifierLE_->DeclareInputInteger(1); lookupIdentifierLE_->DeclareInputInteger(2); lookupIdentifierLE_->DeclareInputString(3); } if (lookupIdentifierGE_.get() == NULL) { lookupIdentifierGE_.reset (new PostgreSQLStatement (*connection_, "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " "d.id = r.internalId AND r.resourceType=$1 AND d.tagGroup=$2 AND d.tagElement=$3 AND d.value>=$4")); lookupIdentifierGE_->DeclareInputInteger(0); lookupIdentifierGE_->DeclareInputInteger(1); lookupIdentifierGE_->DeclareInputInteger(2); lookupIdentifierGE_->DeclareInputString(3); } if (lookupIdentifierWildcard_.get() == NULL) { lookupIdentifierWildcard_.reset (new PostgreSQLStatement (*connection_, "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " "d.id = r.internalId AND r.resourceType=$1 AND d.tagGroup=$2 AND d.tagElement=$3 AND d.value LIKE $4")); lookupIdentifierWildcard_->DeclareInputInteger(0); lookupIdentifierWildcard_->DeclareInputInteger(1); lookupIdentifierWildcard_->DeclareInputInteger(2); lookupIdentifierWildcard_->DeclareInputString(3); } PostgreSQLStatement* statement = NULL; switch (constraint) { case OrthancPluginIdentifierConstraint_Equal: statement = lookupIdentifierEQ_.get(); break; case OrthancPluginIdentifierConstraint_SmallerOrEqual: statement = lookupIdentifierLE_.get(); break; case OrthancPluginIdentifierConstraint_GreaterOrEqual: statement = lookupIdentifierGE_.get(); break; case OrthancPluginIdentifierConstraint_Wildcard: statement = lookupIdentifierWildcard_.get(); break; default: throw PostgreSQLException(); } assert(statement != NULL); statement->BindInteger(0, level); statement->BindInteger(1, group); statement->BindInteger(2, element); if (constraint == OrthancPluginIdentifierConstraint_Wildcard) { statement->BindString(3, ConvertWildcardToLike(value)); } else { statement->BindString(3, value); } PostgreSQLResult result(*statement); target.clear(); while (!result.IsDone()) { target.push_back(result.GetInteger64(0)); result.Step(); } } #endif bool PostgreSQLWrapper::LookupMetadata(std::string& target, int64_t id, int32_t type) { if (lookupMetadata_.get() == NULL) { lookupMetadata_.reset (new PostgreSQLStatement (*connection_, "SELECT value FROM Metadata WHERE id=$1 and type=$2")); lookupMetadata_->DeclareInputInteger64(0); lookupMetadata_->DeclareInputInteger(1); } lookupMetadata_->BindInteger64(0, id); lookupMetadata_->BindInteger(1, static_cast<int>(type)); PostgreSQLResult result(*lookupMetadata_); if (result.IsDone()) { return false; } else { target = result.GetString(0); return true; } } bool PostgreSQLWrapper::LookupParent(int64_t& parentId, int64_t resourceId) { if (lookupParent_.get() == NULL) { lookupParent_.reset (new PostgreSQLStatement (*connection_, "SELECT parentId FROM Resources WHERE internalId=$1")); lookupParent_->DeclareInputInteger64(0); } lookupParent_->BindInteger64(0, resourceId); PostgreSQLResult result(*lookupParent_); if (result.IsDone()) { throw PostgreSQLException("Unknown resource"); } if (result.IsNull(0)) { return false; } else { parentId = result.GetInteger64(0); return true; } } bool PostgreSQLWrapper::LookupResource(int64_t& id, OrthancPluginResourceType& type, const char* publicId) { if (lookupResource_.get() == NULL) { lookupResource_.reset (new PostgreSQLStatement (*connection_, "SELECT internalId, resourceType FROM Resources WHERE publicId=$1")); lookupResource_->DeclareInputString(0); } lookupResource_->BindString(0, publicId); PostgreSQLResult result(*lookupResource_); if (result.IsDone()) { return false; } else { id = result.GetInteger64(0); type = static_cast<OrthancPluginResourceType>(result.GetInteger(1)); return true; } } bool PostgreSQLWrapper::SelectPatientToRecycle(int64_t& internalId) { if (selectPatientToRecycle_.get() == NULL) { selectPatientToRecycle_.reset (new PostgreSQLStatement (*connection_, "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1")); } PostgreSQLResult result(*selectPatientToRecycle_); if (result.IsDone()) { // No patient remaining or all the patients are protected return false; } else { internalId = result.GetInteger64(0); return true; } } bool PostgreSQLWrapper::SelectPatientToRecycle(int64_t& internalId, int64_t patientIdToAvoid) { if (selectPatientToRecycleAvoid_.get() == NULL) { selectPatientToRecycleAvoid_.reset (new PostgreSQLStatement (*connection_, "SELECT patientId FROM PatientRecyclingOrder WHERE patientId != $1 ORDER BY seq ASC LIMIT 1")); selectPatientToRecycleAvoid_->DeclareInputInteger64(0); } selectPatientToRecycleAvoid_->BindInteger64(0, patientIdToAvoid); PostgreSQLResult result(*selectPatientToRecycleAvoid_); if (result.IsDone()) { // No patient remaining or all the patients are protected return false; } else { internalId = result.GetInteger64(0); return true; } } static void SetTagInternal(PostgreSQLStatement& s, int64_t id, uint16_t group, uint16_t element, const char* value) { s.BindInteger64(0, id); s.BindInteger(1, group); s.BindInteger(2, element); s.BindString(3, value); s.Run(); } void PostgreSQLWrapper::SetMainDicomTag(int64_t id, uint16_t group, uint16_t element, const char* value) { if (setMainDicomTags_.get() == NULL) { setMainDicomTags_.reset (new PostgreSQLStatement (*connection_, "INSERT INTO MainDicomTags VALUES($1, $2, $3, $4)")); setMainDicomTags_->DeclareInputInteger64(0); setMainDicomTags_->DeclareInputInteger(1); setMainDicomTags_->DeclareInputInteger(2); if (version_ == 5) { setMainDicomTags_->DeclareInputBinary(3); } else { setMainDicomTags_->DeclareInputString(3); } } SetTagInternal(*setMainDicomTags_, id, group, element, value); } void PostgreSQLWrapper::SetIdentifierTag(int64_t id, uint16_t group, uint16_t element, const char* value) { if (setIdentifierTag_.get() == NULL) { setIdentifierTag_.reset (new PostgreSQLStatement (*connection_, "INSERT INTO DicomIdentifiers VALUES($1, $2, $3, $4)")); setIdentifierTag_->DeclareInputInteger64(0); setIdentifierTag_->DeclareInputInteger(1); setIdentifierTag_->DeclareInputInteger(2); if (version_ == 5) { setIdentifierTag_->DeclareInputBinary(3); } else { setIdentifierTag_->DeclareInputString(3); } } SetTagInternal(*setIdentifierTag_, id, group, element, value); } void PostgreSQLWrapper::SetMetadata(int64_t id, int32_t type, const char* value) { if (setMetadata1_.get() == NULL || setMetadata2_.get() == NULL) { setMetadata1_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM Metadata WHERE id=$1 AND type=$2")); setMetadata1_->DeclareInputInteger64(0); setMetadata1_->DeclareInputInteger(1); setMetadata2_.reset (new PostgreSQLStatement (*connection_, "INSERT INTO Metadata VALUES ($1, $2, $3)")); setMetadata2_->DeclareInputInteger64(0); setMetadata2_->DeclareInputInteger(1); setMetadata2_->DeclareInputString(2); } setMetadata1_->BindInteger64(0, id); setMetadata1_->BindInteger(1, static_cast<int>(type)); setMetadata1_->Run(); setMetadata2_->BindInteger64(0, id); setMetadata2_->BindInteger(1, static_cast<int>(type)); setMetadata2_->BindString(2, value); setMetadata2_->Run(); } void PostgreSQLWrapper::SetProtectedPatient(int64_t internalId, bool isProtected) { if (protectPatient1_.get() == NULL || protectPatient2_.get() == NULL) { protectPatient1_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM PatientRecyclingOrder WHERE patientId=$1")); protectPatient1_->DeclareInputInteger64(0); protectPatient2_.reset (new PostgreSQLStatement (*connection_, "INSERT INTO PatientRecyclingOrder VALUES(DEFAULT, $1)")); protectPatient2_->DeclareInputInteger64(0); } if (isProtected) { protectPatient1_->BindInteger64(0, internalId); protectPatient1_->Run(); } else if (IsProtectedPatient(internalId)) { protectPatient2_->BindInteger64(0, internalId); protectPatient2_->Run(); } else { // Nothing to do: The patient is already unprotected } } // For unit testing only! void PostgreSQLWrapper::GetChildren(std::list<std::string>& childrenPublicIds, int64_t id) { PostgreSQLStatement s(*connection_, "SELECT publicId FROM Resources WHERE parentId=$1"); s.DeclareInputInteger64(0); s.BindInteger64(0, id); PostgreSQLResult result(s); childrenPublicIds.clear(); while (!result.IsDone()) { childrenPublicIds.push_back(result.GetString(0)); result.Step(); } } // For unit testing only! int64_t PostgreSQLWrapper::GetTableRecordCount(const std::string& table) { char buf[128]; sprintf(buf, "SELECT CAST(COUNT(*) AS BIGINT) FROM %s", table.c_str()); PostgreSQLStatement s(*connection_, buf); PostgreSQLResult result(s); if (result.IsDone()) { throw PostgreSQLException(); } if (result.IsNull(0)) { return 0; } else { return result.GetInteger64(0); } } // For unit testing only! bool PostgreSQLWrapper::GetParentPublicId(std::string& target, int64_t id) { PostgreSQLStatement s(*connection_, "SELECT a.publicId FROM Resources AS a, Resources AS b " "WHERE a.internalId = b.parentId AND b.internalId = $1"); s.DeclareInputInteger64(0); s.BindInteger64(0, id); PostgreSQLResult result(s); if (result.IsDone()) { return false; } else { target = result.GetString(0); return true; } } uint32_t PostgreSQLWrapper::GetDatabaseVersion() { // Check the version of the database std::string version = "unknown"; if (!LookupGlobalProperty(version, GlobalProperty_DatabaseSchemaVersion)) { throw PostgreSQLException("The database is corrupted. Drop it manually for Orthanc to recreate it"); } try { return boost::lexical_cast<unsigned int>(version); } catch (boost::bad_lexical_cast&) { throw PostgreSQLException("The database is corrupted. Drop it manually for Orthanc to recreate it"); } } void PostgreSQLWrapper::UpgradeDatabase(uint32_t targetVersion, OrthancPluginStorageArea* storageArea) { unsigned int version = GetDatabaseVersion(); #if !(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER <= 0 && ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER <= 9 && ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER <= 4) if (version == 5 && targetVersion == 6) { version_ = 6; // Must be here, otherwise calls to // SetMainDicomTag() during the reconstruction of // the DICOM tags will fail as the column type // has been altered connection_->Execute("ALTER TABLE MainDicomTags ALTER COLUMN value TYPE TEXT"); connection_->Execute("ALTER TABLE DicomIdentifiers ALTER COLUMN value TYPE TEXT"); OrthancPluginReconstructMainDicomTags(context_, storageArea, OrthancPluginResourceType_Patient); OrthancPluginReconstructMainDicomTags(context_, storageArea, OrthancPluginResourceType_Study); OrthancPluginReconstructMainDicomTags(context_, storageArea, OrthancPluginResourceType_Series); OrthancPluginReconstructMainDicomTags(context_, storageArea, OrthancPluginResourceType_Instance); connection_->Execute("DELETE FROM GlobalProperties WHERE property=1"); connection_->Execute("INSERT INTO GlobalProperties VALUES (1, '6');"); return; // Success } #endif std::string message = ("Unsupported call to upgrade from version " + boost::lexical_cast<std::string>(version) + " to version " + boost::lexical_cast<std::string>(targetVersion) + " of the database schema"); throw PostgreSQLException(message.c_str()); } void PostgreSQLWrapper::ClearMainDicomTags(int64_t id) { if (clearMainDicomTags_.get() == NULL || clearDicomIdentifiers_.get() == NULL) { clearMainDicomTags_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM MainDicomTags WHERE id=$1")); clearMainDicomTags_->DeclareInputInteger64(0); clearDicomIdentifiers_.reset (new PostgreSQLStatement (*connection_, "DELETE FROM DicomIdentifiers WHERE id=$1")); clearDicomIdentifiers_->DeclareInputInteger64(0); } clearMainDicomTags_->BindInteger64(0, id); clearMainDicomTags_->Run(); clearDicomIdentifiers_->BindInteger64(0, id); clearDicomIdentifiers_->Run(); } }