Mercurial > hg > orthanc
diff OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp @ 4591:ff8170d17d90 db-changes
moving all accesses to databases from IDatabaseWrapper to ITransaction
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 15 Mar 2021 15:30:42 +0100 |
parents | bec74e29f86b |
children | 36bbf3169a27 |
line wrap: on
line diff
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Fri Mar 12 16:04:09 2021 +0100 +++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Mon Mar 15 15:30:42 2021 +0100 @@ -39,6 +39,11 @@ #include "../../../OrthancFramework/Sources/SQLite/Transaction.h" #include "../Search/ISqlLookupFormatter.h" #include "../ServerToolbox.h" +#include "Compatibility/ICreateInstance.h" +#include "Compatibility/IGetChildrenMetadata.h" +#include "Compatibility/ILookupResourceAndParent.h" +#include "Compatibility/ISetResourcesContent.h" +#include "VoidDatabaseListener.h" #include <OrthancServerResources.h> @@ -46,85 +51,37 @@ #include <boost/lexical_cast.hpp> namespace Orthanc -{ - class SQLiteDatabaseWrapper::SignalFileDeleted : public SQLite::IScalarFunction +{ + class SQLiteDatabaseWrapper::LookupFormatter : public ISqlLookupFormatter { private: - SQLiteDatabaseWrapper& sqlite_; + std::list<std::string> values_; public: - SignalFileDeleted(SQLiteDatabaseWrapper& sqlite) : - sqlite_(sqlite) - { - } - - virtual const char* GetName() const ORTHANC_OVERRIDE + virtual std::string GenerateParameter(const std::string& value) ORTHANC_OVERRIDE { - return "SignalFileDeleted"; + values_.push_back(value); + return "?"; } - - virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE + + virtual std::string FormatResourceType(ResourceType level) ORTHANC_OVERRIDE { - return 7; + return boost::lexical_cast<std::string>(level); } - virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE + virtual std::string FormatWildcardEscape() ORTHANC_OVERRIDE { - if (sqlite_.listener_ != NULL) - { - std::string uncompressedMD5, compressedMD5; - - if (!context.IsNullValue(5)) - { - uncompressedMD5 = context.GetStringValue(5); - } - - if (!context.IsNullValue(6)) - { - compressedMD5 = context.GetStringValue(6); - } - - FileInfo info(context.GetStringValue(0), - static_cast<FileContentType>(context.GetIntValue(1)), - static_cast<uint64_t>(context.GetInt64Value(2)), - uncompressedMD5, - static_cast<CompressionType>(context.GetIntValue(3)), - static_cast<uint64_t>(context.GetInt64Value(4)), - compressedMD5); - - sqlite_.listener_->SignalAttachmentDeleted(info); - } - } - }; - - - class SQLiteDatabaseWrapper::SignalResourceDeleted : public SQLite::IScalarFunction - { - private: - SQLiteDatabaseWrapper& sqlite_; - - public: - SignalResourceDeleted(SQLiteDatabaseWrapper& sqlite) : - sqlite_(sqlite) - { + return "ESCAPE '\\'"; } - virtual const char* GetName() const ORTHANC_OVERRIDE - { - return "SignalResourceDeleted"; - } - - virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE + void Bind(SQLite::Statement& statement) const { - return 2; - } - - virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE - { - if (sqlite_.listener_ != NULL) + size_t pos = 0; + + for (std::list<std::string>::const_iterator + it = values_.begin(); it != values_.end(); ++it, pos++) { - sqlite_.listener_->SignalResourceDeleted(static_cast<ResourceType>(context.GetIntValue(1)), - context.GetStringValue(0)); + statement.BindString(pos, *it); } } }; @@ -192,147 +149,1100 @@ }; - void SQLiteDatabaseWrapper::GetChangesInternal(std::list<ServerIndexChange>& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults) + class SQLiteDatabaseWrapper::TransactionBase : + public SQLiteDatabaseWrapper::UnitTestsTransaction, + public Compatibility::ICreateInstance, + public Compatibility::IGetChildrenMetadata, + public Compatibility::ILookupResourceAndParent, + public Compatibility::ISetResourcesContent { - target.clear(); - - while (target.size() < maxResults && s.Step()) + private: + void AnswerLookup(std::list<std::string>& resourcesId, + std::list<std::string>& instancesId, + ResourceType level) { - int64_t seq = s.ColumnInt64(0); - ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); - ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); - const std::string& date = s.ColumnString(4); + resourcesId.clear(); + instancesId.clear(); + + std::unique_ptr<SQLite::Statement> statement; + + switch (level) + { + case ResourceType_Patient: + { + statement.reset( + new SQLite::Statement( + db_, SQLITE_FROM_HERE, + "SELECT patients.publicId, instances.publicID FROM Lookup AS patients " + "INNER JOIN Resources studies ON patients.internalId=studies.parentId " + "INNER JOIN Resources series ON studies.internalId=series.parentId " + "INNER JOIN Resources instances ON series.internalId=instances.parentId " + "GROUP BY patients.publicId")); + + break; + } - int64_t internalId = s.ColumnInt64(2); - std::string publicId = GetPublicId(internalId); + case ResourceType_Study: + { + statement.reset( + new SQLite::Statement( + db_, SQLITE_FROM_HERE, + "SELECT studies.publicId, instances.publicID FROM Lookup AS studies " + "INNER JOIN Resources series ON studies.internalId=series.parentId " + "INNER JOIN Resources instances ON series.internalId=instances.parentId " + "GROUP BY studies.publicId")); + + break; + } - target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); + case ResourceType_Series: + { + statement.reset( + new SQLite::Statement( + db_, SQLITE_FROM_HERE, + "SELECT series.publicId, instances.publicID FROM Lookup AS series " + "INNER JOIN Resources instances ON series.internalId=instances.parentId " + "GROUP BY series.publicId")); + + break; + } + + case ResourceType_Instance: + { + statement.reset( + new SQLite::Statement( + db_, SQLITE_FROM_HERE, "SELECT publicId, publicId FROM Lookup")); + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + assert(statement.get() != NULL); + + while (statement->Step()) + { + resourcesId.push_back(statement->ColumnString(0)); + instancesId.push_back(statement->ColumnString(1)); + } } - done = !(target.size() == maxResults && s.Step()); - } + + void ClearTable(const std::string& tableName) + { + db_.Execute("DELETE FROM " + tableName); + } + + + void GetChangesInternal(std::list<ServerIndexChange>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults) + { + target.clear(); + + while (target.size() < maxResults && s.Step()) + { + int64_t seq = s.ColumnInt64(0); + ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); + ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); + const std::string& date = s.ColumnString(4); + + int64_t internalId = s.ColumnInt64(2); + std::string publicId = GetPublicId(internalId); + + target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); + } + + done = !(target.size() == maxResults && s.Step()); + } - void SQLiteDatabaseWrapper::GetExportedResourcesInternal(std::list<ExportedResource>& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults) - { - target.clear(); - - while (target.size() < maxResults && s.Step()) + void GetExportedResourcesInternal(std::list<ExportedResource>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults) { - int64_t seq = s.ColumnInt64(0); - ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1)); - std::string publicId = s.ColumnString(2); + target.clear(); + + while (target.size() < maxResults && s.Step()) + { + int64_t seq = s.ColumnInt64(0); + ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1)); + std::string publicId = s.ColumnString(2); + + ExportedResource resource(seq, + resourceType, + publicId, + s.ColumnString(3), // modality + s.ColumnString(8), // date + s.ColumnString(4), // patient ID + s.ColumnString(5), // study instance UID + s.ColumnString(6), // series instance UID + s.ColumnString(7)); // sop instance UID + + target.push_back(resource); + } + + done = !(target.size() == maxResults && s.Step()); + } + - ExportedResource resource(seq, - resourceType, - publicId, - s.ColumnString(3), // modality - s.ColumnString(8), // date - s.ColumnString(4), // patient ID - s.ColumnString(5), // study instance UID - s.ColumnString(6), // series instance UID - s.ColumnString(7)); // sop instance UID + void GetChildren(std::list<std::string>& childrenPublicIds, + int64_t id) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?"); + s.BindInt64(0, id); + + childrenPublicIds.clear(); + while (s.Step()) + { + childrenPublicIds.push_back(s.ColumnString(0)); + } + } - target.push_back(resource); + IDatabaseListener& listener_; + SignalRemainingAncestor& signalRemainingAncestor_; + + public: + TransactionBase(SQLite::Connection& db, + IDatabaseListener& listener, + SignalRemainingAncestor& signalRemainingAncestor) : + UnitTestsTransaction(db), + listener_(listener), + signalRemainingAncestor_(signalRemainingAncestor) + { + } + + IDatabaseListener& GetListener() const + { + return listener_; } - done = !(target.size() == maxResults && s.Step()); - } + + virtual void AddAttachment(int64_t id, + const FileInfo& attachment) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, attachment.GetContentType()); + s.BindString(2, attachment.GetUuid()); + s.BindInt64(3, attachment.GetCompressedSize()); + s.BindInt64(4, attachment.GetUncompressedSize()); + s.BindInt(5, attachment.GetCompressionType()); + s.BindString(6, attachment.GetUncompressedMD5()); + s.BindString(7, attachment.GetCompressedMD5()); + s.Run(); + } + + + virtual void ApplyLookupResources(std::list<std::string>& resourcesId, + std::list<std::string>* instancesId, + const std::vector<DatabaseConstraint>& lookup, + ResourceType queryLevel, + size_t limit) ORTHANC_OVERRIDE + { + LookupFormatter formatter; + + std::string sql; + LookupFormatter::Apply(sql, formatter, lookup, queryLevel, limit); + + sql = "CREATE TEMPORARY TABLE Lookup AS " + sql; + + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup"); + s.Run(); + } + + { + SQLite::Statement statement(db_, sql); + formatter.Bind(statement); + statement.Run(); + } + + if (instancesId != NULL) + { + AnswerLookup(resourcesId, *instancesId, queryLevel); + } + else + { + resourcesId.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Lookup"); + + while (s.Step()) + { + resourcesId.push_back(s.ColumnString(0)); + } + } + } + + + virtual void AttachChild(int64_t parent, + int64_t child) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?"); + s.BindInt64(0, parent); + s.BindInt64(1, child); + s.Run(); + } + + + virtual void ClearChanges() ORTHANC_OVERRIDE + { + ClearTable("Changes"); + } + + virtual void ClearExportedResources() ORTHANC_OVERRIDE + { + ClearTable("ExportedResources"); + } - void SQLiteDatabaseWrapper::GetChildren(std::list<std::string>& childrenPublicIds, - int64_t id) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?"); - s.BindInt64(0, id); + virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE + { + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?"); + s.BindInt64(0, id); + s.Run(); + } + + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?"); + s.BindInt64(0, id); + s.Run(); + } + } + + + virtual bool CreateInstance(CreateInstanceResult& result, + int64_t& instanceId, + const std::string& patient, + const std::string& study, + const std::string& series, + const std::string& instance) ORTHANC_OVERRIDE + { + return ICreateInstance::Apply + (*this, result, instanceId, patient, study, series, instance); + } + - childrenPublicIds.clear(); - while (s.Step()) + virtual int64_t CreateResource(const std::string& publicId, + ResourceType type) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)"); + s.BindInt(0, type); + s.BindString(1, publicId); + s.Run(); + return db_.GetLastInsertRowId(); + } + + + virtual void DeleteAttachment(int64_t id, + FileContentType attachment) ORTHANC_OVERRIDE { - childrenPublicIds.push_back(s.ColumnString(0)); + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, attachment); + s.Run(); } - } + + + virtual void DeleteMetadata(int64_t id, + MetadataType type) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); + s.BindInt64(0, id); + s.BindInt(1, type); + s.Run(); + } - void SQLiteDatabaseWrapper::DeleteResource(int64_t id) - { - signalRemainingAncestor_->Reset(); + virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE + { + signalRemainingAncestor_.Reset(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?"); + s.BindInt64(0, id); + s.Run(); + + if (signalRemainingAncestor_.HasRemainingAncestor()) + { + listener_.SignalRemainingAncestor(signalRemainingAncestor_.GetRemainingAncestorType(), + signalRemainingAncestor_.GetRemainingAncestorId()); + } + } + - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?"); - s.BindInt64(0, id); - s.Run(); + virtual void GetAllMetadata(std::map<MetadataType, std::string>& target, + int64_t id) ORTHANC_OVERRIDE + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type, value FROM Metadata WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + MetadataType key = static_cast<MetadataType>(s.ColumnInt(0)); + target[key] = s.ColumnString(1); + } + } + - if (signalRemainingAncestor_->HasRemainingAncestor() && - listener_ != NULL) + virtual void GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + + virtual void GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit) ORTHANC_OVERRIDE { - listener_->SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorType(), - signalRemainingAncestor_->GetRemainingAncestorId()); + if (limit == 0) + { + target.clear(); + return; + } + + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT publicId FROM Resources WHERE " + "resourceType=? LIMIT ? OFFSET ?"); + s.BindInt(0, resourceType); + s.BindInt64(1, limit); + s.BindInt64(2, since); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } } - } - bool SQLiteDatabaseWrapper::GetParentPublicId(std::string& target, - int64_t id) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " - "WHERE a.internalId = b.parentId AND b.internalId = ?"); - s.BindInt64(0, id); + virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/, + bool& done /*out*/, + int64_t since, + uint32_t maxResults) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); + s.BindInt64(0, since); + s.BindInt(1, maxResults + 1); + GetChangesInternal(target, done, s, maxResults); + } - if (s.Step()) + + virtual void GetChildrenMetadata(std::list<std::string>& target, + int64_t resourceId, + MetadataType metadata) ORTHANC_OVERRIDE + { + IGetChildrenMetadata::Apply(*this, target, resourceId, metadata); + } + + + virtual void GetChildrenInternalId(std::list<int64_t>& target, + int64_t id) ORTHANC_OVERRIDE { - target = s.ColumnString(0); - return true; + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ?"); + s.BindInt64(0, id); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnInt64(0)); + } } - else + + + virtual void GetChildrenPublicId(std::list<std::string>& target, + int64_t id) ORTHANC_OVERRIDE { - return false; + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ?"); + s.BindInt64(0, id); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } } - } + + + virtual void GetExportedResources(std::list<ExportedResource>& target, + bool& done, + int64_t since, + uint32_t maxResults) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); + s.BindInt64(0, since); + s.BindInt(1, maxResults + 1); + GetExportedResourcesInternal(target, done, s, maxResults); + } + + + virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE + { + bool done; // Ignored + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); + GetChangesInternal(target, done, s, 1); + } - int64_t SQLiteDatabaseWrapper::GetTableRecordCount(const std::string& table) - { - /** - * "Generally one cannot use SQL parameters/placeholders for - * database identifiers (tables, columns, views, schemas, etc.) or - * database functions (e.g., CURRENT_DATE), but instead only for - * binding literal values." => To avoid any SQL injection, we - * check that the "table" parameter has only alphabetic - * characters. - * https://stackoverflow.com/a/1274764/881731 - **/ - for (size_t i = 0; i < table.size(); i++) + int64_t GetLastChangeIndex() ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT seq FROM sqlite_sequence WHERE name='Changes'"); + + if (s.Step()) + { + int64_t c = s.ColumnInt(0); + assert(!s.Step()); + return c; + } + else + { + // No change has been recorded so far in the database + return 0; + } + } + + + virtual void GetLastExportedResource(std::list<ExportedResource>& target) ORTHANC_OVERRIDE + { + bool done; // Ignored + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); + GetExportedResourcesInternal(target, done, s, 1); + } + + + virtual void GetMainDicomTags(DicomMap& map, + int64_t id) ORTHANC_OVERRIDE { - if (!isalpha(table[i])) + map.Clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?"); + s.BindInt64(0, id); + while (s.Step()) { - throw OrthancException(ErrorCode_ParameterOutOfRange); + map.SetValue(s.ColumnInt(1), + s.ColumnInt(2), + s.ColumnString(3), false); + } + } + + + virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT publicId FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (s.Step()) + { + return s.ColumnString(0); + } + else + { + throw OrthancException(ErrorCode_UnknownResource); + } + } + + + virtual uint64_t GetResourceCount(ResourceType resourceType) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT COUNT(*) FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + if (!s.Step()) + { + return 0; + } + else + { + int64_t c = s.ColumnInt(0); + assert(!s.Step()); + return c; } } - // Don't use "SQLITE_FROM_HERE", otherwise "table" would be cached - SQLite::Statement s(db_, "SELECT COUNT(*) FROM " + table); - if (s.Step()) + virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE { - int64_t c = s.ColumnInt(0); - assert(!s.Step()); - return c; + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT resourceType FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (s.Step()) + { + return static_cast<ResourceType>(s.ColumnInt(0)); + } + else + { + throw OrthancException(ErrorCode_UnknownResource); + } } - else + + + virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE { - throw OrthancException(ErrorCode_InternalError); + // Old SQL query that was used in Orthanc <= 1.5.0: + // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles"); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=0"); + s.Run(); + return static_cast<uint64_t>(s.ColumnInt64(0)); } - } + virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE + { + // Old SQL query that was used in Orthanc <= 1.5.0: + // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles"); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=1"); + s.Run(); + return static_cast<uint64_t>(s.ColumnInt64(0)); + } + + + virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE + { + return GetTotalCompressedSize() > threshold; + } + + + virtual bool IsExistingResource(int64_t internalId) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM Resources WHERE internalId=?"); + s.BindInt64(0, internalId); + return s.Step(); + } + + + virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?"); + s.BindInt64(0, internalId); + return !s.Step(); + } + + + virtual void ListAvailableAttachments(std::set<FileContentType>& target, + int64_t id) ORTHANC_OVERRIDE + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT fileType FROM AttachedFiles WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + target.insert(static_cast<FileContentType>(s.ColumnInt(0))); + } + } + + + virtual void LogChange(int64_t internalId, + const ServerIndexChange& change) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); + s.BindInt(0, change.GetChangeType()); + s.BindInt64(1, internalId); + s.BindInt(2, change.GetResourceType()); + s.BindString(3, change.GetDate()); + s.Run(); + } + + + virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)"); + + s.BindInt(0, resource.GetResourceType()); + s.BindString(1, resource.GetPublicId()); + s.BindString(2, resource.GetModality()); + s.BindString(3, resource.GetPatientId()); + s.BindString(4, resource.GetStudyInstanceUid()); + s.BindString(5, resource.GetSeriesInstanceUid()); + s.BindString(6, resource.GetSopInstanceUid()); + s.BindString(7, resource.GetDate()); + s.Run(); + } + + + virtual bool LookupAttachment(FileInfo& attachment, + int64_t id, + FileContentType contentType) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT uuid, uncompressedSize, compressionType, compressedSize, " + "uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, contentType); + + if (!s.Step()) + { + return false; + } + else + { + attachment = FileInfo(s.ColumnString(0), + contentType, + s.ColumnInt64(1), + s.ColumnString(4), + static_cast<CompressionType>(s.ColumnInt(2)), + s.ColumnInt64(3), + s.ColumnString(5)); + return true; + } + } + + + virtual bool LookupGlobalProperty(std::string& target, + GlobalProperty property) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT value FROM GlobalProperties WHERE property=?"); + s.BindInt(0, property); + + if (!s.Step()) + { + return false; + } + else + { + target = s.ColumnString(0); + return true; + } + } + + + virtual bool LookupMetadata(std::string& target, + int64_t id, + MetadataType type) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT value FROM Metadata WHERE id=? AND type=?"); + s.BindInt64(0, id); + s.BindInt(1, type); + + if (!s.Step()) + { + return false; + } + else + { + target = s.ColumnString(0); + return true; + } + } + + + virtual bool LookupParent(int64_t& parentId, + int64_t resourceId) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT parentId FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (!s.Step()) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + if (s.ColumnIsNull(0)) + { + return false; + } + else + { + parentId = s.ColumnInt(0); + return true; + } + } + + + virtual bool LookupResourceAndParent(int64_t& id, + ResourceType& type, + std::string& parentPublicId, + const std::string& publicId) ORTHANC_OVERRIDE + { + return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId); + } + + + virtual bool LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT internalId, resourceType FROM Resources WHERE publicId=?"); + s.BindString(0, publicId); + + if (!s.Step()) + { + return false; + } + else + { + id = s.ColumnInt(0); + type = static_cast<ResourceType>(s.ColumnInt(1)); + + // Check whether there is a single resource with this public id + assert(!s.Step()); + + return true; + } + } + + + virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1"); + + if (!s.Step()) + { + // No patient remaining or all the patients are protected + return false; + } + else + { + internalId = s.ColumnInt(0); + return true; + } + } + + + virtual bool SelectPatientToRecycle(int64_t& internalId, + int64_t patientIdToAvoid) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT patientId FROM PatientRecyclingOrder " + "WHERE patientId != ? ORDER BY seq ASC LIMIT 1"); + s.BindInt64(0, patientIdToAvoid); + + if (!s.Step()) + { + // No patient remaining or all the patients are protected + return false; + } + else + { + internalId = s.ColumnInt(0); + return true; + } + } + + + virtual void SetGlobalProperty(GlobalProperty property, + const std::string& value) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)"); + s.BindInt(0, property); + s.BindString(1, value); + s.Run(); + } + + + virtual void SetIdentifierTag(int64_t id, + const DicomTag& tag, + const std::string& value) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, tag.GetGroup()); + s.BindInt(2, tag.GetElement()); + s.BindString(3, value); + s.Run(); + } + + + virtual void SetProtectedPatient(int64_t internalId, + bool isProtected) ORTHANC_OVERRIDE + { + if (isProtected) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); + s.BindInt64(0, internalId); + s.Run(); + } + else if (IsProtectedPatient(internalId)) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); + s.BindInt64(0, internalId); + s.Run(); + } + else + { + // Nothing to do: The patient is already unprotected + } + } + + + virtual void SetMainDicomTag(int64_t id, + const DicomTag& tag, + const std::string& value) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, tag.GetGroup()); + s.BindInt(2, tag.GetElement()); + s.BindString(3, value); + s.Run(); + } + + + virtual void SetMetadata(int64_t id, + MetadataType type, + const std::string& value) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, type); + s.BindString(2, value); + s.Run(); + } + + + virtual void SetResourcesContent(const Orthanc::ResourcesContent& content) ORTHANC_OVERRIDE + { + ISetResourcesContent::Apply(*this, content); + } + + + virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE + { + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); + s.BindInt64(0, patient); + s.Run(); + + assert(db_.GetLastChangeCount() == 0 || + db_.GetLastChangeCount() == 1); + + if (db_.GetLastChangeCount() == 0) + { + // The patient was protected, there was nothing to delete from the recycling order + return; + } + } + + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); + s.BindInt64(0, patient); + s.Run(); + } + } + }; + + + class SQLiteDatabaseWrapper::SignalFileDeleted : public SQLite::IScalarFunction + { + private: + SQLiteDatabaseWrapper& sqlite_; + + public: + SignalFileDeleted(SQLiteDatabaseWrapper& sqlite) : + sqlite_(sqlite) + { + } + + virtual const char* GetName() const ORTHANC_OVERRIDE + { + return "SignalFileDeleted"; + } + + virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE + { + return 7; + } + + virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE + { + if (sqlite_.activeTransaction_ != NULL) + { + std::string uncompressedMD5, compressedMD5; + + if (!context.IsNullValue(5)) + { + uncompressedMD5 = context.GetStringValue(5); + } + + if (!context.IsNullValue(6)) + { + compressedMD5 = context.GetStringValue(6); + } + + FileInfo info(context.GetStringValue(0), + static_cast<FileContentType>(context.GetIntValue(1)), + static_cast<uint64_t>(context.GetInt64Value(2)), + uncompressedMD5, + static_cast<CompressionType>(context.GetIntValue(3)), + static_cast<uint64_t>(context.GetInt64Value(4)), + compressedMD5); + + sqlite_.activeTransaction_->GetListener().SignalAttachmentDeleted(info); + } + } + }; + + + class SQLiteDatabaseWrapper::SignalResourceDeleted : public SQLite::IScalarFunction + { + private: + SQLiteDatabaseWrapper& sqlite_; + + public: + SignalResourceDeleted(SQLiteDatabaseWrapper& sqlite) : + sqlite_(sqlite) + { + } + + virtual const char* GetName() const ORTHANC_OVERRIDE + { + return "SignalResourceDeleted"; + } + + virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE + { + return 2; + } + + virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE + { + if (sqlite_.activeTransaction_ != NULL) + { + sqlite_.activeTransaction_->GetListener(). + SignalResourceDeleted(static_cast<ResourceType>(context.GetIntValue(1)), + context.GetStringValue(0)); + } + } + }; + + + class SQLiteDatabaseWrapper::ReadWriteTransaction : public SQLiteDatabaseWrapper::TransactionBase + { + private: + SQLiteDatabaseWrapper& that_; + std::unique_ptr<SQLite::Transaction> transaction_; + int64_t initialDiskSize_; + + public: + ReadWriteTransaction(SQLiteDatabaseWrapper& that, + IDatabaseListener& listener) : + TransactionBase(that.db_, listener, *that.signalRemainingAncestor_), + that_(that), + transaction_(new SQLite::Transaction(that_.db_)) + { + if (that_.activeTransaction_ != NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + that_.activeTransaction_ = this; + +#if defined(NDEBUG) + // Release mode + initialDiskSize_ = 0; +#else + // Debug mode + initialDiskSize_ = static_cast<int64_t>(GetTotalCompressedSize()); +#endif + } + + virtual ~ReadWriteTransaction() + { + assert(that_.activeTransaction_ != NULL); + that_.activeTransaction_ = NULL; + } + + void Begin() + { + transaction_->Begin(); + } + + virtual void Rollback() ORTHANC_OVERRIDE + { + transaction_->Rollback(); + } + + virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE + { + transaction_->Commit(); + + assert(initialDiskSize_ + fileSizeDelta >= 0 && + initialDiskSize_ + fileSizeDelta == static_cast<int64_t>(GetTotalCompressedSize())); + } + }; + + + class SQLiteDatabaseWrapper::ReadOnlyTransaction : public SQLiteDatabaseWrapper::TransactionBase + { + private: + SQLiteDatabaseWrapper& that_; + + public: + ReadOnlyTransaction(SQLiteDatabaseWrapper& that, + IDatabaseListener& listener) : + TransactionBase(that.db_, listener, *that.signalRemainingAncestor_), + that_(that) + { + if (that_.activeTransaction_ != NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + that_.activeTransaction_ = this; + } + + virtual ~ReadOnlyTransaction() + { + assert(that_.activeTransaction_ != NULL); + that_.activeTransaction_ = NULL; + } + + virtual void Rollback() ORTHANC_OVERRIDE + { + } + + virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE + { + if (fileSizeDelta != 0) + { + throw OrthancException(ErrorCode_InternalError); + } + } + }; + + SQLiteDatabaseWrapper::SQLiteDatabaseWrapper(const std::string& path) : - listener_(NULL), + activeTransaction_(NULL), signalRemainingAncestor_(NULL), version_(0) { @@ -341,41 +1251,33 @@ SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() : - listener_(NULL), + activeTransaction_(NULL), signalRemainingAncestor_(NULL), version_(0) { db_.OpenInMemory(); } - - int SQLiteDatabaseWrapper::GetGlobalIntegerProperty(GlobalProperty property, - int defaultValue) + SQLiteDatabaseWrapper::~SQLiteDatabaseWrapper() { - std::string tmp; - - if (!LookupGlobalProperty(tmp, GlobalProperty_DatabasePatchLevel)) + if (activeTransaction_ != NULL) { - return defaultValue; - } - else - { - try - { - return boost::lexical_cast<int>(tmp); - } - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Global property " + boost::lexical_cast<std::string>(property) + - " should be an integer, but found: " + tmp); - } + LOG(ERROR) << "A SQLite transaction is still active in the SQLiteDatabaseWrapper destructor: Expect a crash"; } } void SQLiteDatabaseWrapper::Open() { + if (signalRemainingAncestor_ != NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); // Cannot open twice + } + + signalRemainingAncestor_ = dynamic_cast<SignalRemainingAncestor*>(db_.Register(new SignalRemainingAncestor)); + db_.Register(new SignalFileDeleted(*this)); + db_.Register(new SignalResourceDeleted(*this)); + db_.Execute("PRAGMA ENCODING=\"UTF-8\";"); // Performance tuning of SQLite with PRAGMAs @@ -388,10 +1290,11 @@ // Make "LIKE" case-sensitive in SQLite db_.Execute("PRAGMA case_sensitive_like = true;"); - + + VoidDatabaseListener listener; + { - SQLite::Transaction t(db_); - t.Begin(); + std::unique_ptr<ITransaction> transaction(StartTransaction(TransactionType_ReadOnly, listener)); if (!db_.DoesTableExist("GlobalProperties")) { @@ -403,7 +1306,7 @@ // Check the version of the database std::string tmp; - if (!LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion)) + if (!transaction->LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion)) { tmp = "Unknown"; } @@ -428,7 +1331,7 @@ // New in Orthanc 1.5.1 if (version_ == 6) { - if (!LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) || + if (!transaction->LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) || tmp != "1") { LOG(INFO) << "Installing the SQLite triggers to track the size of the attachments"; @@ -438,12 +1341,8 @@ } } - t.Commit(); + transaction->Commit(0); } - - signalRemainingAncestor_ = dynamic_cast<SignalRemainingAncestor*>(db_.Register(new SignalRemainingAncestor)); - db_.Register(new SignalFileDeleted(*this)); - db_.Register(new SignalResourceDeleted(*this)); } @@ -496,188 +1395,25 @@ // No change in the DB schema, the step from version 5 to 6 only // consists in reconstructing the main DICOM tags information // (as more tags got included). - db_.BeginTransaction(); - ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Patient); - ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Study); - ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Series); - ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Instance); - db_.Execute("UPDATE GlobalProperties SET value=\"6\" WHERE property=" + - boost::lexical_cast<std::string>(GlobalProperty_DatabaseSchemaVersion) + ";"); - db_.CommitTransaction(); + + VoidDatabaseListener listener; + + { + std::unique_ptr<ITransaction> transaction(StartTransaction(TransactionType_ReadWrite, listener)); + ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Patient); + ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Study); + ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Series); + ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Instance); + db_.Execute("UPDATE GlobalProperties SET value=\"6\" WHERE property=" + + boost::lexical_cast<std::string>(GlobalProperty_DatabaseSchemaVersion) + ";"); + transaction->Commit(0); + } + version_ = 6; } } - void SQLiteDatabaseWrapper::ClearTable(const std::string& tableName) - { - db_.Execute("DELETE FROM " + tableName); - } - - - bool SQLiteDatabaseWrapper::LookupParent(int64_t& parentId, - int64_t resourceId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT parentId FROM Resources WHERE internalId=?"); - s.BindInt64(0, resourceId); - - if (!s.Step()) - { - throw OrthancException(ErrorCode_UnknownResource); - } - - if (s.ColumnIsNull(0)) - { - return false; - } - else - { - parentId = s.ColumnInt(0); - return true; - } - } - - - ResourceType SQLiteDatabaseWrapper::GetResourceType(int64_t resourceId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT resourceType FROM Resources WHERE internalId=?"); - s.BindInt64(0, resourceId); - - if (s.Step()) - { - return static_cast<ResourceType>(s.ColumnInt(0)); - } - else - { - throw OrthancException(ErrorCode_UnknownResource); - } - } - - - std::string SQLiteDatabaseWrapper::GetPublicId(int64_t resourceId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT publicId FROM Resources WHERE internalId=?"); - s.BindInt64(0, resourceId); - - if (s.Step()) - { - return s.ColumnString(0); - } - else - { - throw OrthancException(ErrorCode_UnknownResource); - } - } - - - void SQLiteDatabaseWrapper::GetChanges(std::list<ServerIndexChange>& target /*out*/, - bool& done /*out*/, - int64_t since, - uint32_t maxResults) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); - s.BindInt64(0, since); - s.BindInt(1, maxResults + 1); - GetChangesInternal(target, done, s, maxResults); - } - - - void SQLiteDatabaseWrapper::GetLastChange(std::list<ServerIndexChange>& target /*out*/) - { - bool done; // Ignored - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); - GetChangesInternal(target, done, s, 1); - } - - - class SQLiteDatabaseWrapper::ReadWriteTransaction : public IDatabaseWrapper::ITransaction - { - private: - SQLiteDatabaseWrapper& that_; - std::unique_ptr<SQLite::Transaction> transaction_; - int64_t initialDiskSize_; - - public: - ReadWriteTransaction(SQLiteDatabaseWrapper& that, - IDatabaseListener& listener) : - that_(that), - transaction_(new SQLite::Transaction(that_.db_)) - { - assert(that_.listener_ == NULL); - that_.listener_ = &listener; // TODO - STORE IN TRANSACTION - -#if defined(NDEBUG) - // Release mode - initialDiskSize_ = 0; -#else - // Debug mode - initialDiskSize_ = static_cast<int64_t>(that_.GetTotalCompressedSize()); -#endif - } - - virtual ~ReadWriteTransaction() - { - assert(that_.listener_ != NULL); - that_.listener_ = NULL; // TODO - STORE IN TRANSACTION - } - - void Begin() - { - transaction_->Begin(); - } - - virtual void Rollback() ORTHANC_OVERRIDE - { - transaction_->Rollback(); - } - - virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE - { - transaction_->Commit(); - - assert(initialDiskSize_ + fileSizeDelta >= 0 && - initialDiskSize_ + fileSizeDelta == static_cast<int64_t>(that_.GetTotalCompressedSize())); - } - }; - - - class SQLiteDatabaseWrapper::ReadOnlyTransaction : public IDatabaseWrapper::ITransaction - { - private: - SQLiteDatabaseWrapper& that_; - - public: - ReadOnlyTransaction(SQLiteDatabaseWrapper& that, - IDatabaseListener& listener) : - that_(that) - { - assert(that_.listener_ == NULL); - that_.listener_ = &listener; - } - - virtual ~ReadOnlyTransaction() - { - assert(that_.listener_ != NULL); - that_.listener_ = NULL; - } - - virtual void Rollback() ORTHANC_OVERRIDE - { - } - - virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE - { - if (fileSizeDelta != 0) - { - throw OrthancException(ErrorCode_InternalError); - } - } - }; - - IDatabaseWrapper::ITransaction* SQLiteDatabaseWrapper::StartTransaction(TransactionType type, IDatabaseListener& listener) { @@ -699,54 +1435,9 @@ } } - - void SQLiteDatabaseWrapper::GetAllMetadata(std::map<MetadataType, std::string>& target, - int64_t id) - { - target.clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type, value FROM Metadata WHERE id=?"); - s.BindInt64(0, id); - - while (s.Step()) - { - MetadataType key = static_cast<MetadataType>(s.ColumnInt(0)); - target[key] = s.ColumnString(1); - } - } - - - void SQLiteDatabaseWrapper::SetGlobalProperty(GlobalProperty property, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)"); - s.BindInt(0, property); - s.BindString(1, value); - s.Run(); - } - - - bool SQLiteDatabaseWrapper::LookupGlobalProperty(std::string& target, - GlobalProperty property) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT value FROM GlobalProperties WHERE property=?"); - s.BindInt(0, property); - - if (!s.Step()) - { - return false; - } - else - { - target = s.ColumnString(0); - return true; - } - } - - - int64_t SQLiteDatabaseWrapper::CreateResource(const std::string& publicId, - ResourceType type) + + int64_t SQLiteDatabaseWrapper::UnitTestsTransaction::CreateResource(const std::string& publicId, + ResourceType type) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)"); s.BindInt(0, type); @@ -756,33 +1447,8 @@ } - bool SQLiteDatabaseWrapper::LookupResource(int64_t& id, - ResourceType& type, - const std::string& publicId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT internalId, resourceType FROM Resources WHERE publicId=?"); - s.BindString(0, publicId); - - if (!s.Step()) - { - return false; - } - else - { - id = s.ColumnInt(0); - type = static_cast<ResourceType>(s.ColumnInt(1)); - - // Check whether there is a single resource with this public id - assert(!s.Step()); - - return true; - } - } - - - void SQLiteDatabaseWrapper::AttachChild(int64_t parent, - int64_t child) + void SQLiteDatabaseWrapper::UnitTestsTransaction::AttachChild(int64_t parent, + int64_t child) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?"); s.BindInt64(0, parent); @@ -791,150 +1457,9 @@ } - void SQLiteDatabaseWrapper::SetMetadata(int64_t id, - MetadataType type, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, type); - s.BindString(2, value); - s.Run(); - } - - - void SQLiteDatabaseWrapper::DeleteMetadata(int64_t id, - MetadataType type) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); - s.BindInt64(0, id); - s.BindInt(1, type); - s.Run(); - } - - - bool SQLiteDatabaseWrapper::LookupMetadata(std::string& target, - int64_t id, - MetadataType type) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT value FROM Metadata WHERE id=? AND type=?"); - s.BindInt64(0, id); - s.BindInt(1, type); - - if (!s.Step()) - { - return false; - } - else - { - target = s.ColumnString(0); - return true; - } - } - - - void SQLiteDatabaseWrapper::AddAttachment(int64_t id, - const FileInfo& attachment) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, attachment.GetContentType()); - s.BindString(2, attachment.GetUuid()); - s.BindInt64(3, attachment.GetCompressedSize()); - s.BindInt64(4, attachment.GetUncompressedSize()); - s.BindInt(5, attachment.GetCompressionType()); - s.BindString(6, attachment.GetUncompressedMD5()); - s.BindString(7, attachment.GetCompressedMD5()); - s.Run(); - } - - - void SQLiteDatabaseWrapper::DeleteAttachment(int64_t id, - FileContentType attachment) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); - s.BindInt64(0, id); - s.BindInt(1, attachment); - s.Run(); - } - - - void SQLiteDatabaseWrapper::ListAvailableAttachments(std::set<FileContentType>& target, - int64_t id) - { - target.clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT fileType FROM AttachedFiles WHERE id=?"); - s.BindInt64(0, id); - - while (s.Step()) - { - target.insert(static_cast<FileContentType>(s.ColumnInt(0))); - } - } - - bool SQLiteDatabaseWrapper::LookupAttachment(FileInfo& attachment, - int64_t id, - FileContentType contentType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT uuid, uncompressedSize, compressionType, compressedSize, " - "uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); - s.BindInt64(0, id); - s.BindInt(1, contentType); - - if (!s.Step()) - { - return false; - } - else - { - attachment = FileInfo(s.ColumnString(0), - contentType, - s.ColumnInt64(1), - s.ColumnString(4), - static_cast<CompressionType>(s.ColumnInt(2)), - s.ColumnInt64(3), - s.ColumnString(5)); - return true; - } - } - - - void SQLiteDatabaseWrapper::ClearMainDicomTags(int64_t id) - { - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?"); - s.BindInt64(0, id); - s.Run(); - } - - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?"); - s.BindInt64(0, id); - s.Run(); - } - } - - - void SQLiteDatabaseWrapper::SetMainDicomTag(int64_t id, - const DicomTag& tag, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, tag.GetGroup()); - s.BindInt(2, tag.GetElement()); - s.BindString(3, value); - s.Run(); - } - - - void SQLiteDatabaseWrapper::SetIdentifierTag(int64_t id, - const DicomTag& tag, - const std::string& value) + void SQLiteDatabaseWrapper::UnitTestsTransaction::SetIdentifierTag(int64_t id, + const DicomTag& tag, + const std::string& value) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)"); s.BindInt64(0, id); @@ -945,427 +1470,40 @@ } - void SQLiteDatabaseWrapper::GetMainDicomTags(DicomMap& map, - int64_t id) + void SQLiteDatabaseWrapper::UnitTestsTransaction::SetMainDicomTag(int64_t id, + const DicomTag& tag, + const std::string& value) { - map.Clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?"); - s.BindInt64(0, id); - while (s.Step()) - { - map.SetValue(s.ColumnInt(1), - s.ColumnInt(2), - s.ColumnString(3), false); - } - } - - - void SQLiteDatabaseWrapper::GetChildrenPublicId(std::list<std::string>& target, - int64_t id) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " - "WHERE a.parentId = b.internalId AND b.internalId = ?"); + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); s.BindInt64(0, id); - - target.clear(); - - while (s.Step()) - { - target.push_back(s.ColumnString(0)); - } - } - - - void SQLiteDatabaseWrapper::GetChildrenInternalId(std::list<int64_t>& target, - int64_t id) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b " - "WHERE a.parentId = b.internalId AND b.internalId = ?"); - s.BindInt64(0, id); - - target.clear(); - - while (s.Step()) - { - target.push_back(s.ColumnInt64(0)); - } - } - - - void SQLiteDatabaseWrapper::LogChange(int64_t internalId, - const ServerIndexChange& change) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); - s.BindInt(0, change.GetChangeType()); - s.BindInt64(1, internalId); - s.BindInt(2, change.GetResourceType()); - s.BindString(3, change.GetDate()); + s.BindInt(1, tag.GetGroup()); + s.BindInt(2, tag.GetElement()); + s.BindString(3, value); s.Run(); } - void SQLiteDatabaseWrapper::LogExportedResource(const ExportedResource& resource) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)"); - - s.BindInt(0, resource.GetResourceType()); - s.BindString(1, resource.GetPublicId()); - s.BindString(2, resource.GetModality()); - s.BindString(3, resource.GetPatientId()); - s.BindString(4, resource.GetStudyInstanceUid()); - s.BindString(5, resource.GetSeriesInstanceUid()); - s.BindString(6, resource.GetSopInstanceUid()); - s.BindString(7, resource.GetDate()); - s.Run(); - } - - - void SQLiteDatabaseWrapper::GetExportedResources(std::list<ExportedResource>& target, - bool& done, - int64_t since, - uint32_t maxResults) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); - s.BindInt64(0, since); - s.BindInt(1, maxResults + 1); - GetExportedResourcesInternal(target, done, s, maxResults); - } - - - void SQLiteDatabaseWrapper::GetLastExportedResource(std::list<ExportedResource>& target) - { - bool done; // Ignored - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); - GetExportedResourcesInternal(target, done, s, 1); - } - - - uint64_t SQLiteDatabaseWrapper::GetTotalCompressedSize() - { - // Old SQL query that was used in Orthanc <= 1.5.0: - // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles"); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=0"); - s.Run(); - return static_cast<uint64_t>(s.ColumnInt64(0)); - } - - - uint64_t SQLiteDatabaseWrapper::GetTotalUncompressedSize() + int64_t SQLiteDatabaseWrapper::UnitTestsTransaction::GetTableRecordCount(const std::string& table) { - // Old SQL query that was used in Orthanc <= 1.5.0: - // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles"); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=1"); - s.Run(); - return static_cast<uint64_t>(s.ColumnInt64(0)); - } - - - uint64_t SQLiteDatabaseWrapper::GetResourceCount(ResourceType resourceType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT COUNT(*) FROM Resources WHERE resourceType=?"); - s.BindInt(0, resourceType); - - if (!s.Step()) - { - return 0; - } - else + /** + * "Generally one cannot use SQL parameters/placeholders for + * database identifiers (tables, columns, views, schemas, etc.) or + * database functions (e.g., CURRENT_DATE), but instead only for + * binding literal values." => To avoid any SQL injection, we + * check that the "table" parameter has only alphabetic + * characters. + * https://stackoverflow.com/a/1274764/881731 + **/ + for (size_t i = 0; i < table.size(); i++) { - int64_t c = s.ColumnInt(0); - assert(!s.Step()); - return c; - } - } - - - void SQLiteDatabaseWrapper::GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?"); - s.BindInt(0, resourceType); - - target.clear(); - while (s.Step()) - { - target.push_back(s.ColumnString(0)); - } - } - - - void SQLiteDatabaseWrapper::GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType, - size_t since, - size_t limit) - { - if (limit == 0) - { - target.clear(); - return; + if (!isalpha(table[i])) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } } - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT publicId FROM Resources WHERE " - "resourceType=? LIMIT ? OFFSET ?"); - s.BindInt(0, resourceType); - s.BindInt64(1, limit); - s.BindInt64(2, since); - - target.clear(); - while (s.Step()) - { - target.push_back(s.ColumnString(0)); - } - } - - - bool SQLiteDatabaseWrapper::SelectPatientToRecycle(int64_t& internalId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1"); - - if (!s.Step()) - { - // No patient remaining or all the patients are protected - return false; - } - else - { - internalId = s.ColumnInt(0); - return true; - } - } - - - bool SQLiteDatabaseWrapper::SelectPatientToRecycle(int64_t& internalId, - int64_t patientIdToAvoid) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT patientId FROM PatientRecyclingOrder " - "WHERE patientId != ? ORDER BY seq ASC LIMIT 1"); - s.BindInt64(0, patientIdToAvoid); - - if (!s.Step()) - { - // No patient remaining or all the patients are protected - return false; - } - else - { - internalId = s.ColumnInt(0); - return true; - } - } - - - bool SQLiteDatabaseWrapper::IsProtectedPatient(int64_t internalId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?"); - s.BindInt64(0, internalId); - return !s.Step(); - } - - - void SQLiteDatabaseWrapper::SetProtectedPatient(int64_t internalId, - bool isProtected) - { - if (isProtected) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); - s.BindInt64(0, internalId); - s.Run(); - } - else if (IsProtectedPatient(internalId)) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); - s.BindInt64(0, internalId); - s.Run(); - } - else - { - // Nothing to do: The patient is already unprotected - } - } - - - bool SQLiteDatabaseWrapper::IsExistingResource(int64_t internalId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM Resources WHERE internalId=?"); - s.BindInt64(0, internalId); - return s.Step(); - } - - - bool SQLiteDatabaseWrapper::IsDiskSizeAbove(uint64_t threshold) - { - return GetTotalCompressedSize() > threshold; - } - - - - class SQLiteDatabaseWrapper::LookupFormatter : public ISqlLookupFormatter - { - private: - std::list<std::string> values_; - - public: - virtual std::string GenerateParameter(const std::string& value) ORTHANC_OVERRIDE - { - values_.push_back(value); - return "?"; - } - - virtual std::string FormatResourceType(ResourceType level) ORTHANC_OVERRIDE - { - return boost::lexical_cast<std::string>(level); - } - - virtual std::string FormatWildcardEscape() ORTHANC_OVERRIDE - { - return "ESCAPE '\\'"; - } - - void Bind(SQLite::Statement& statement) const - { - size_t pos = 0; - - for (std::list<std::string>::const_iterator - it = values_.begin(); it != values_.end(); ++it, pos++) - { - statement.BindString(pos, *it); - } - } - }; - - - static void AnswerLookup(std::list<std::string>& resourcesId, - std::list<std::string>& instancesId, - SQLite::Connection& db, - ResourceType level) - { - resourcesId.clear(); - instancesId.clear(); - - std::unique_ptr<SQLite::Statement> statement; - - switch (level) - { - case ResourceType_Patient: - { - statement.reset( - new SQLite::Statement( - db, SQLITE_FROM_HERE, - "SELECT patients.publicId, instances.publicID FROM Lookup AS patients " - "INNER JOIN Resources studies ON patients.internalId=studies.parentId " - "INNER JOIN Resources series ON studies.internalId=series.parentId " - "INNER JOIN Resources instances ON series.internalId=instances.parentId " - "GROUP BY patients.publicId")); - - break; - } - - case ResourceType_Study: - { - statement.reset( - new SQLite::Statement( - db, SQLITE_FROM_HERE, - "SELECT studies.publicId, instances.publicID FROM Lookup AS studies " - "INNER JOIN Resources series ON studies.internalId=series.parentId " - "INNER JOIN Resources instances ON series.internalId=instances.parentId " - "GROUP BY studies.publicId")); - - break; - } - - case ResourceType_Series: - { - statement.reset( - new SQLite::Statement( - db, SQLITE_FROM_HERE, - "SELECT series.publicId, instances.publicID FROM Lookup AS series " - "INNER JOIN Resources instances ON series.internalId=instances.parentId " - "GROUP BY series.publicId")); - - break; - } - - case ResourceType_Instance: - { - statement.reset( - new SQLite::Statement( - db, SQLITE_FROM_HERE, "SELECT publicId, publicId FROM Lookup")); - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - - assert(statement.get() != NULL); - - while (statement->Step()) - { - resourcesId.push_back(statement->ColumnString(0)); - instancesId.push_back(statement->ColumnString(1)); - } - } - - - void SQLiteDatabaseWrapper::ApplyLookupResources(std::list<std::string>& resourcesId, - std::list<std::string>* instancesId, - const std::vector<DatabaseConstraint>& lookup, - ResourceType queryLevel, - size_t limit) - { - LookupFormatter formatter; - - std::string sql; - LookupFormatter::Apply(sql, formatter, lookup, queryLevel, limit); - - sql = "CREATE TEMPORARY TABLE Lookup AS " + sql; - - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup"); - s.Run(); - } - - { - SQLite::Statement statement(db_, sql); - formatter.Bind(statement); - statement.Run(); - } - - if (instancesId != NULL) - { - AnswerLookup(resourcesId, *instancesId, db_, queryLevel); - } - else - { - resourcesId.clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Lookup"); - - while (s.Step()) - { - resourcesId.push_back(s.ColumnString(0)); - } - } - } - - - int64_t SQLiteDatabaseWrapper::GetLastChangeIndex() - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT seq FROM sqlite_sequence WHERE name='Changes'"); + // Don't use "SQLITE_FROM_HERE", otherwise "table" would be cached + SQLite::Statement s(db_, "SELECT COUNT(*) FROM " + table); if (s.Step()) { @@ -1375,35 +1513,40 @@ } else { - // No change has been recorded so far in the database - return 0; + throw OrthancException(ErrorCode_InternalError); } } - void SQLiteDatabaseWrapper::TagMostRecentPatient(int64_t patient) + bool SQLiteDatabaseWrapper::UnitTestsTransaction::GetParentPublicId(std::string& target, + int64_t id) { - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); - s.BindInt64(0, patient); - s.Run(); + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " + "WHERE a.internalId = b.parentId AND b.internalId = ?"); + s.BindInt64(0, id); - assert(db_.GetLastChangeCount() == 0 || - db_.GetLastChangeCount() == 1); - - if (db_.GetLastChangeCount() == 0) - { - // The patient was protected, there was nothing to delete from the recycling order - return; - } + if (s.Step()) + { + target = s.ColumnString(0); + return true; + } + else + { + return false; } + } + + void SQLiteDatabaseWrapper::UnitTestsTransaction::GetChildren(std::list<std::string>& childrenPublicIds, + int64_t id) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?"); + s.BindInt64(0, id); + + childrenPublicIds.clear(); + while (s.Step()) { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); - s.BindInt64(0, patient); - s.Run(); + childrenPublicIds.push_back(s.ColumnString(0)); } } }