# HG changeset patch # User Alain Mazy # Date 1712854362 -7200 # Node ID 594859656a064542c7df03ea773c34ddbbbca8a8 # Parent c27071770c0472c9f4676200dd085d0674ab1a0c Added support for ExtendedApiV1: /changes diff -r c27071770c04 -r 594859656a06 Framework/Common/DatabaseManager.cpp --- a/Framework/Common/DatabaseManager.cpp Tue Apr 09 15:47:30 2024 +0200 +++ b/Framework/Common/DatabaseManager.cpp Thu Apr 11 18:52:42 2024 +0200 @@ -78,9 +78,9 @@ } - IPrecompiledStatement* DatabaseManager::LookupCachedStatement(const StatementLocation& location) const + IPrecompiledStatement* DatabaseManager::LookupCachedStatement(const StatementId& statementId) const { - CachedStatements::const_iterator found = cachedStatements_.find(location); + CachedStatements::const_iterator found = cachedStatements_.find(statementId); if (found == cachedStatements_.end()) { @@ -94,10 +94,10 @@ } - IPrecompiledStatement& DatabaseManager::CacheStatement(const StatementLocation& location, + IPrecompiledStatement& DatabaseManager::CacheStatement(const StatementId& statementId, const Query& query) { - LOG(TRACE) << "Caching statement from " << location.GetFile() << ":" << location.GetLine(); + LOG(TRACE) << "Caching statement from " << statementId.GetFile() << ":" << statementId.GetLine() << "" << statementId.GetDynamicStatement(); std::unique_ptr statement(GetDatabase().Compile(query)); @@ -107,8 +107,8 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - assert(cachedStatements_.find(location) == cachedStatements_.end()); - cachedStatements_[location] = statement.release(); + assert(cachedStatements_.find(statementId) == cachedStatements_.end()); + cachedStatements_[statementId] = statement.release(); return *tmp; } @@ -550,13 +550,13 @@ } - DatabaseManager::CachedStatement::CachedStatement(const StatementLocation& location, + DatabaseManager::CachedStatement::CachedStatement(const StatementId& statementId, DatabaseManager& manager, const std::string& sql) : StatementBase(manager), - location_(location) + statementId_(statementId) { - statement_ = GetManager().LookupCachedStatement(location_); + statement_ = GetManager().LookupCachedStatement(statementId_); if (statement_ == NULL) { @@ -565,7 +565,7 @@ else { LOG(TRACE) << "Reusing cached statement from " - << location_.GetFile() << ":" << location_.GetLine(); + << statementId_.GetFile() << ":" << statementId_.GetLine() << " " << statementId_.GetDynamicStatement(); } } @@ -579,7 +579,7 @@ { // Register the newly-created statement assert(statement_ == NULL); - statement_ = &GetManager().CacheStatement(location_, *query); + statement_ = &GetManager().CacheStatement(statementId_, *query); } assert(statement_ != NULL); diff -r c27071770c04 -r 594859656a06 Framework/Common/DatabaseManager.h --- a/Framework/Common/DatabaseManager.h Tue Apr 09 15:47:30 2024 +0200 +++ b/Framework/Common/DatabaseManager.h Thu Apr 11 18:52:42 2024 +0200 @@ -23,7 +23,7 @@ #pragma once #include "IDatabaseFactory.h" -#include "StatementLocation.h" +#include "StatementId.h" #include // For std::unique_ptr<> #include @@ -48,7 +48,7 @@ class DatabaseManager : public boost::noncopyable { private: - typedef std::map CachedStatements; + typedef std::map CachedStatements; std::unique_ptr factory_; std::unique_ptr database_; @@ -58,9 +58,9 @@ void CloseIfUnavailable(Orthanc::ErrorCode e); - IPrecompiledStatement* LookupCachedStatement(const StatementLocation& location) const; + IPrecompiledStatement* LookupCachedStatement(const StatementId& statementId) const; - IPrecompiledStatement& CacheStatement(const StatementLocation& location, + IPrecompiledStatement& CacheStatement(const StatementId& statementId, const Query& query); ITransaction& GetTransaction(); @@ -206,11 +206,11 @@ class CachedStatement : public StatementBase { private: - StatementLocation location_; + StatementId statementId_; IPrecompiledStatement* statement_; public: - CachedStatement(const StatementLocation& location, + CachedStatement(const StatementId& statementId, DatabaseManager& manager, const std::string& sql); diff -r c27071770c04 -r 594859656a06 Framework/Common/StatementId.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Common/StatementId.cpp Thu Apr 11 18:52:42 2024 +0200 @@ -0,0 +1,43 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2024 Osimis S.A., Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "StatementId.h" + +#include + +namespace OrthancDatabases +{ + bool StatementId::operator< (const StatementId& other) const + { + if (line_ != other.line_) + { + return line_ < other.line_; + } + + if (strcmp(file_, other.file_) < 0) + { + return true; + } + + return statement_ < other.statement_; + } +} diff -r c27071770c04 -r 594859656a06 Framework/Common/StatementId.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Common/StatementId.h Thu Apr 11 18:52:42 2024 +0200 @@ -0,0 +1,76 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2024 Osimis S.A., Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include + +#define STATEMENT_FROM_HERE ::OrthancDatabases::StatementId(__FILE__, __LINE__) +#define STATEMENT_FROM_HERE_DYNAMIC(sql) ::OrthancDatabases::StatementId(__FILE__, __LINE__, sql) + + +namespace OrthancDatabases +{ + class StatementId + { + private: + const char* file_; + int line_; + std::string statement_; + + StatementId(); // Forbidden + + public: + StatementId(const char* file, + int line) : + file_(file), + line_(line) + { + } + + StatementId(const char* file, + int line, + const std::string& statement) : + file_(file), + line_(line), + statement_(statement) + { + } + + const char* GetFile() const + { + return file_; + } + + int GetLine() const + { + return line_; + } + + const std::string& GetDynamicStatement() const + { + return statement_; + } + + bool operator< (const StatementId& other) const; + }; +} diff -r c27071770c04 -r 594859656a06 Framework/Common/StatementLocation.cpp --- a/Framework/Common/StatementLocation.cpp Tue Apr 09 15:47:30 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2024 Osimis S.A., Belgium - * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "StatementLocation.h" - -#include - -namespace OrthancDatabases -{ - bool StatementLocation::operator< (const StatementLocation& other) const - { - if (line_ != other.line_) - { - return line_ < other.line_; - } - else - { - return strcmp(file_, other.file_) < 0; - } - } -} diff -r c27071770c04 -r 594859656a06 Framework/Common/StatementLocation.h --- a/Framework/Common/StatementLocation.h Tue Apr 09 15:47:30 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2024 Osimis S.A., Belgium - * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#define STATEMENT_FROM_HERE ::OrthancDatabases::StatementLocation(__FILE__, __LINE__) - - -namespace OrthancDatabases -{ - class StatementLocation - { - private: - const char* file_; - int line_; - - StatementLocation(); // Forbidden - - public: - StatementLocation(const char* file, - int line) : - file_(file), - line_(line) - { - } - - const char* GetFile() const - { - return file_; - } - - int GetLine() const - { - return line_; - } - - bool operator< (const StatementLocation& other) const; - }; -} diff -r c27071770c04 -r 594859656a06 Framework/Plugins/DatabaseBackendAdapterV4.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV4.cpp Tue Apr 09 15:47:30 2024 +0200 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp Thu Apr 11 18:52:42 2024 +0200 @@ -438,6 +438,9 @@ response.mutable_get_system_information()->set_has_measure_latency(accessor.GetBackend().HasMeasureLatency()); #endif +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 13, 0) + response.mutable_get_system_information()->set_has_extended_api_v1(accessor.GetBackend().HasExtendedApiV1()); +#endif break; } @@ -782,7 +785,19 @@ response.mutable_get_changes()->set_done(done); break; } - +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 13, 0) + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHANGES_2: + { + Output output(*response.mutable_get_changes()); + + bool done; + backend.GetChanges2(output, done, manager, request.get_changes2().since(), request.get_changes2().to(), static_cast(request.get_changes2().change_type()), request.get_changes2().limit()); + + response.mutable_get_changes()->set_done(done); + break; + } +#endif + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_INTERNAL_ID: { std::list values; diff -r c27071770c04 -r 594859656a06 Framework/Plugins/IDatabaseBackend.h --- a/Framework/Plugins/IDatabaseBackend.h Tue Apr 09 15:47:30 2024 +0200 +++ b/Framework/Plugins/IDatabaseBackend.h Thu Apr 11 18:52:42 2024 +0200 @@ -109,6 +109,14 @@ int64_t since, uint32_t limit) = 0; + virtual void GetChanges2(IDatabaseBackendOutput& output, + bool& done /*out*/, + DatabaseManager& manager, + int64_t since, + int64_t to, + int32_t changeType, + uint32_t limit) = 0; + virtual void GetChildrenInternalId(std::list& target /*out*/, DatabaseManager& manager, int64_t id) = 0; @@ -376,6 +384,8 @@ // New in Orthanc 1.12.3 virtual uint64_t MeasureLatency(DatabaseManager& manager) = 0; + // New in Orthanc 1.13.0 + virtual bool HasExtendedApiV1() = 0; }; } diff -r c27071770c04 -r 594859656a06 Framework/Plugins/IndexBackend.cpp --- a/Framework/Plugins/IndexBackend.cpp Tue Apr 09 15:47:30 2024 +0200 +++ b/Framework/Plugins/IndexBackend.cpp Thu Apr 11 18:52:42 2024 +0200 @@ -116,28 +116,60 @@ DatabaseManager& manager, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t limit) + uint32_t limit, + bool returnFirstResults) { + struct Change + { + int64_t seq_; + int32_t changeType_; + OrthancPluginResourceType resourceType_; + std::string publicId_; + std::string changeDate_; + + Change(int64_t seq, int32_t changeType, OrthancPluginResourceType resourceType, const std::string& publicId, const std::string& changeDate) + : seq_(seq), changeType_(changeType), resourceType_(resourceType), publicId_(publicId), changeDate_(changeDate) + { + } + }; + statement.Execute(args); - uint32_t count = 0; - - while (count < limit && - !statement.IsDone()) + std::list changes; + while (!statement.IsDone()) { - output.AnswerChange( + changes.push_back(Change( statement.ReadInteger64(0), statement.ReadInteger32(1), static_cast(statement.ReadInteger32(2)), statement.ReadString(3), - statement.ReadString(4)); + statement.ReadString(4) + )); statement.Next(); - count++; } - - done = (count < limit || - statement.IsDone()); + + done = changes.size() <= limit; // 'done' means we have returned all requested changes + + // if we have retrieved more changes than requested -> cleanup + if (changes.size() > limit) + { + assert(changes.size() == limit+1); // the statement should only request 1 element more + + if (returnFirstResults) + { + changes.pop_back(); + } + else + { + changes.pop_front(); + } + } + + for (std::list::const_iterator it = changes.begin(); it != changes.end(); ++it) + { + output.AnswerChange(it->seq_, it->changeType_, it->resourceType_, it->publicId_, it->changeDate_); + } } @@ -553,39 +585,113 @@ ReadListOfStrings(target, statement, args); } - - /* Use GetOutput().AnswerChange() */ void IndexBackend::GetChanges(IDatabaseBackendOutput& output, bool& done /*out*/, DatabaseManager& manager, int64_t since, uint32_t limit) { - std::string suffix; +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 13, 0) + GetChanges2(output, done, manager, since, -1, _OrthancPluginChangeType_All, limit); +#else + GetChanges2(output, done, manager, since, -1, 65535, limit); +#endif + } + + /* Use GetOutput().AnswerChange() */ + void IndexBackend::GetChanges2(IDatabaseBackendOutput& output, + bool& done /*out*/, + DatabaseManager& manager, + int64_t since, + int64_t to, + int32_t changeType, + uint32_t limit) + { + std::string limitSuffix; if (manager.GetDialect() == Dialect_MSSQL) { - suffix = "OFFSET 0 ROWS FETCH FIRST ${limit} ROWS ONLY"; + limitSuffix = "OFFSET 0 ROWS FETCH FIRST ${limit} ROWS ONLY"; } else { - suffix = "LIMIT ${limit}"; + limitSuffix = "LIMIT ${limit}"; } - DatabaseManager::CachedStatement statement( - STATEMENT_FROM_HERE, manager, - "SELECT Changes.seq, Changes.changeType, Changes.resourceType, Resources.publicId, " - "Changes.date FROM Changes INNER JOIN Resources " - "ON Changes.internalId = Resources.internalId WHERE seq>${since} ORDER BY seq " + suffix); - + std::vector filters; + bool hasSince = false; + bool hasTo = false; + bool hasFilterType = false; + + if (since > 0) + { + hasSince = true; + filters.push_back("seq>${since}"); + } + if (to != -1) + { + hasTo = true; + filters.push_back("seq<=${to}"); + } + if (changeType != _OrthancPluginChangeType_All) + { + hasFilterType = true; + filters.push_back("changeType=${changeType}"); + } + + std::string filtersString; + if (filters.size() > 0) + { + Orthanc::Toolbox::JoinStrings(filtersString, filters, " AND "); + filtersString = "WHERE " + filtersString; + } + + std::string sql; + bool returnFirstResults; + if (hasTo && !hasSince) + { + // in this case, we want the largest values but we want them ordered in ascending order + sql = "SELECT * FROM (SELECT Changes.seq, Changes.changeType, Changes.resourceType, Resources.publicId, Changes.date " + "FROM Changes INNER JOIN Resources " + "ON Changes.internalId = Resources.internalId " + filtersString + " ORDER BY seq DESC " + limitSuffix + + ") AS FilteredChanges ORDER BY seq ASC"; + + returnFirstResults = false; + } + else + { + // default query: we want the smallest values ordered in ascending order + sql = "SELECT Changes.seq, Changes.changeType, Changes.resourceType, Resources.publicId, " + "Changes.date FROM Changes INNER JOIN Resources " + "ON Changes.internalId = Resources.internalId " + filtersString + " ORDER BY seq ASC " + limitSuffix; + returnFirstResults = true; + } + + DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE_DYNAMIC(sql), manager, sql); statement.SetReadOnly(true); + Dictionary args; + statement.SetParameterType("limit", ValueType_Integer64); - statement.SetParameterType("since", ValueType_Integer64); - - Dictionary args; - args.SetIntegerValue("limit", limit + 1); - args.SetIntegerValue("since", since); - - ReadChangesInternal(output, done, manager, statement, args, limit); + args.SetIntegerValue("limit", limit + 1); // we take limit+1 because we use the +1 to know if "Done" must be set to true + + if (hasSince) + { + statement.SetParameterType("since", ValueType_Integer64); + args.SetIntegerValue("since", since); + } + + if (hasTo) + { + statement.SetParameterType("to", ValueType_Integer64); + args.SetIntegerValue("to", to); + } + + if (hasFilterType) + { + statement.SetParameterType("changeType", ValueType_Integer64); + args.SetIntegerValue("changeType", changeType); + } + + ReadChangesInternal(output, done, manager, statement, args, limit, returnFirstResults); } @@ -685,7 +791,7 @@ Dictionary args; bool done; // Ignored - ReadChangesInternal(output, done, manager, statement, args, 1); + ReadChangesInternal(output, done, manager, statement, args, 1, true); } diff -r c27071770c04 -r 594859656a06 Framework/Plugins/IndexBackend.h --- a/Framework/Plugins/IndexBackend.h Tue Apr 09 15:47:30 2024 +0200 +++ b/Framework/Plugins/IndexBackend.h Thu Apr 11 18:52:42 2024 +0200 @@ -64,7 +64,8 @@ DatabaseManager& manager, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t limit); + uint32_t limit, + bool returnFirstResults); void ReadExportedResourcesInternal(IDatabaseBackendOutput& output, bool& done, @@ -129,7 +130,15 @@ DatabaseManager& manager, int64_t since, uint32_t limit) ORTHANC_OVERRIDE; - + + virtual void GetChanges2(IDatabaseBackendOutput& output, + bool& done /*out*/, + DatabaseManager& manager, + int64_t since, + int64_t to, + int32_t changeType, + uint32_t limit) ORTHANC_OVERRIDE; + virtual void GetChildrenInternalId(std::list& target /*out*/, DatabaseManager& manager, int64_t id) ORTHANC_OVERRIDE; @@ -419,6 +428,13 @@ virtual uint64_t MeasureLatency(DatabaseManager& manager) ORTHANC_OVERRIDE; + // New primitive since Orthanc 1.13.0 + virtual bool HasExtendedApiV1() ORTHANC_OVERRIDE + { + return true; + } + + /** * "maxDatabaseRetries" is to handle * "OrthancPluginErrorCode_DatabaseCannotSerialize" if there is a diff -r c27071770c04 -r 594859656a06 MySQL/NEWS --- a/MySQL/NEWS Tue Apr 09 15:47:30 2024 +0200 +++ b/MySQL/NEWS Thu Apr 11 18:52:42 2024 +0200 @@ -7,6 +7,8 @@ Optimal Orthanc runtime: 1.12.0+ * Fix check of Orthanc runtime version +* Added support for ExtendedApiV1: + - changes?type=...&to=... Release 5.1 (2023-06-27) diff -r c27071770c04 -r 594859656a06 Odbc/NEWS --- a/Odbc/NEWS Tue Apr 09 15:47:30 2024 +0200 +++ b/Odbc/NEWS Thu Apr 11 18:52:42 2024 +0200 @@ -7,6 +7,8 @@ Optimal Orthanc runtime: 1.12.0+ * Fix check of Orthanc runtime version +* Added support for ExtendedApiV1: + - changes?type=...&to=... Release 1.2 (2024-03-06) diff -r c27071770c04 -r 594859656a06 PostgreSQL/NEWS --- a/PostgreSQL/NEWS Tue Apr 09 15:47:30 2024 +0200 +++ b/PostgreSQL/NEWS Thu Apr 11 18:52:42 2024 +0200 @@ -6,6 +6,9 @@ Minimum Orthanc runtime: 1.12.3 * Fix updates from plugin version 3.3 to latest version +* Added support for ExtendedApiV1: + - changes?type=...&to=... + Release 6.2 (2024-03-25) diff -r c27071770c04 -r 594859656a06 Resources/CMake/DatabasesFrameworkConfiguration.cmake --- a/Resources/CMake/DatabasesFrameworkConfiguration.cmake Tue Apr 09 15:47:30 2024 +0200 +++ b/Resources/CMake/DatabasesFrameworkConfiguration.cmake Thu Apr 11 18:52:42 2024 +0200 @@ -119,7 +119,7 @@ ${ORTHANC_DATABASES_ROOT}/Framework/Common/ResultFileValue.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/RetryDatabaseFactory.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/RetryDatabaseFactory.cpp - ${ORTHANC_DATABASES_ROOT}/Framework/Common/StatementLocation.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Common/StatementId.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/Utf8StringValue.cpp )