Mercurial > hg > orthanc-databases
changeset 771:5019e732058d
merged pg-next-699 -> default
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Fri, 28 Nov 2025 15:38:54 +0100 |
| parents | da9d68c3bf6b (current diff) bd6fcb94d68f (diff) |
| children | 36f4a9992d11 |
| files | |
| diffstat | 23 files changed, 584 insertions(+), 192 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Plugins/DatabaseBackendAdapterV4.cpp Wed Nov 26 14:20:16 2025 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp Fri Nov 28 15:38:54 2025 +0100 @@ -464,6 +464,10 @@ response.mutable_get_system_information()->set_supports_queues(accessor.GetBackend().HasQueues()); #endif +#if ORTHANC_PLUGINS_HAS_RESERVE_QUEUE_VALUE == 1 + response.mutable_get_system_information()->set_supports_reserve_queue_value(accessor.GetBackend().HasReserveQueueValue()); +#endif + #if ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES == 1 response.mutable_get_system_information()->set_supports_key_value_stores(accessor.GetBackend().HasKeyValueStores()); #endif @@ -1391,6 +1395,37 @@ #endif +#if ORTHANC_PLUGINS_HAS_RESERVE_QUEUE_VALUE == 1 + case Orthanc::DatabasePluginMessages::OPERATION_RESERVE_QUEUE_VALUE: + { + std::string value; + uint64_t valueId; + bool found = backend.ReserveQueueValue(value, valueId, manager, + request.reserve_queue_value().queue_id(), + request.reserve_queue_value().origin() == Orthanc::DatabasePluginMessages::QUEUE_ORIGIN_FRONT, + request.reserve_queue_value().release_timeout()); + response.mutable_reserve_queue_value()->set_found(found); + + if (found) + { + response.mutable_reserve_queue_value()->set_value(value); + response.mutable_reserve_queue_value()->set_value_id(valueId); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_ACKNOWLEDGE_QUEUE_VALUE: + { + backend.AcknowledgeQueueValue(manager, + request.acknowledge_queue_value().queue_id(), + request.acknowledge_queue_value().value_id()); + + break; + } + +#endif + #if ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA == 1 case Orthanc::DatabasePluginMessages::OPERATION_GET_ATTACHMENT_CUSTOM_DATA: {
--- a/Framework/Plugins/IDatabaseBackend.h Wed Nov 26 14:20:16 2025 +0100 +++ b/Framework/Plugins/IDatabaseBackend.h Fri Nov 28 15:38:54 2025 +0100 @@ -139,6 +139,8 @@ virtual bool HasQueues() const = 0; + virtual bool HasReserveQueueValue() const = 0; + virtual bool HasAuditLogs() const = 0; virtual void AddAttachment(DatabaseManager& manager, @@ -525,6 +527,19 @@ const std::string& queueId) = 0; #endif +#if ORTHANC_PLUGINS_HAS_RESERVE_QUEUE_VALUE == 1 + virtual bool ReserveQueueValue(std::string& value, + uint64_t& valueId, + DatabaseManager& manager, + const std::string& queueId, + bool fromFront, + uint32_t reserveTimeout) = 0; + + virtual void AcknowledgeQueueValue(DatabaseManager& manager, + const std::string& queueId, + uint64_t valueId) = 0; +#endif + #if ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA == 1 virtual void GetAttachmentCustomData(std::string& customData, DatabaseManager& manager,
--- a/Framework/Plugins/ISqlLookupFormatter.h Wed Nov 26 14:20:16 2025 +0100 +++ b/Framework/Plugins/ISqlLookupFormatter.h Fri Nov 28 15:38:54 2025 +0100 @@ -30,6 +30,7 @@ #pragma once +#include "../Common/Dictionary.h" #include "MessagesToolbox.h" #include <boost/noncopyable.hpp> @@ -79,6 +80,8 @@ virtual std::string FormatFloatCast() const = 0; + virtual const Dictionary& GetDictionary() const = 0; + static void GetLookupLevels(Orthanc::ResourceType& lowerLevel, Orthanc::ResourceType& upperLevel, const Orthanc::ResourceType& queryLevel,
--- a/Framework/Plugins/IndexBackend.cpp Wed Nov 26 14:20:16 2025 +0100 +++ b/Framework/Plugins/IndexBackend.cpp Fri Nov 28 15:38:54 2025 +0100 @@ -39,6 +39,15 @@ namespace OrthancDatabases { + static int64_t GetSecondsSinceEpoch() + { + // https://www.boost.org/doc/libs/1_69_0/doc/html/date_time/examples.html#date_time.examples.seconds_since_epoch + static const boost::posix_time::ptime EPOCH(boost::gregorian::date(1970, 1, 1)); + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + return (now - EPOCH).total_seconds(); + } + + static std::string ConvertWildcardToLike(const std::string& query) { std::string s = query; @@ -4505,30 +4514,38 @@ } - bool IndexBackend::DequeueValueSQLite(std::string& value, - DatabaseManager& manager, - const std::string& queueId, - bool fromFront) + bool IndexBackend::DequeueValue(std::string& value, + DatabaseManager& manager, + const std::string& queueId, + bool fromFront) { - assert(manager.GetDialect() == Dialect_SQLite); - LookupFormatter formatter(manager.GetDialect()); std::unique_ptr<DatabaseManager::CachedStatement> statement; std::string queueIdParameter = formatter.GenerateParameter(queueId); - - if (fromFront) + std::string nowParameter = formatter.GenerateParameter(GetSecondsSinceEpoch()); + + switch (manager.GetDialect()) { - statement.reset(new DatabaseManager::CachedStatement( - STATEMENT_FROM_HERE, manager, - "SELECT id, value FROM Queues WHERE queueId=" + queueIdParameter + " ORDER BY id ASC LIMIT 1")); - } - else - { - statement.reset(new DatabaseManager::CachedStatement( - STATEMENT_FROM_HERE, manager, - "SELECT id, value FROM Queues WHERE queueId=" + queueIdParameter + " ORDER BY id DESC LIMIT 1")); + case Dialect_PostgreSQL: + if (fromFront) + { + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "WITH poppedRows AS (DELETE FROM Queues WHERE id = (SELECT MIN(id) FROM Queues WHERE queueId=" + queueIdParameter + " AND (reservedUntil IS NULL OR reservedUntil <= " + nowParameter + ")) RETURNING value) " + "SELECT value FROM poppedRows")); + } + else + { + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "WITH poppedRows AS (DELETE FROM Queues WHERE id = (SELECT MAX(id) FROM Queues WHERE queueId=" + queueIdParameter + " AND (reservedUntil IS NULL OR reservedUntil <= " + nowParameter + ")) RETURNING value) " + "SELECT value FROM poppedRows")); + } + break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } statement->Execute(formatter.GetDictionary()); @@ -4539,83 +4556,12 @@ } else { - statement->SetResultFieldType(0, ValueType_Integer64); - statement->SetResultFieldType(1, ValueType_BinaryString); - - value = statement->ReadString(1); - - { - DatabaseManager::CachedStatement s2(STATEMENT_FROM_HERE, manager, - "DELETE FROM Queues WHERE id=${id}"); - - s2.SetParameterType("id", ValueType_Integer64); - - Dictionary args; - args.SetIntegerValue("id", statement->ReadInteger64(0)); - - s2.Execute(args); - } - + statement->SetResultFieldType(0, ValueType_BinaryString); + value = statement->ReadString(0); return true; } } - - bool IndexBackend::DequeueValue(std::string& value, - DatabaseManager& manager, - const std::string& queueId, - bool fromFront) - { - if (manager.GetDialect() == Dialect_SQLite) - { - return DequeueValueSQLite(value, manager, queueId, fromFront); - } - else - { - LookupFormatter formatter(manager.GetDialect()); - - std::unique_ptr<DatabaseManager::CachedStatement> statement; - - std::string queueIdParameter = formatter.GenerateParameter(queueId); - - switch (manager.GetDialect()) - { - case Dialect_PostgreSQL: - if (fromFront) - { - statement.reset(new DatabaseManager::CachedStatement( - STATEMENT_FROM_HERE, manager, - "WITH poppedRows AS (DELETE FROM Queues WHERE id = (SELECT MIN(id) FROM Queues WHERE queueId=" + queueIdParameter + ") RETURNING value) " - "SELECT value FROM poppedRows")); - } - else - { - statement.reset(new DatabaseManager::CachedStatement( - STATEMENT_FROM_HERE, manager, - "WITH poppedRows AS (DELETE FROM Queues WHERE id = (SELECT MAX(id) FROM Queues WHERE queueId=" + queueIdParameter + ") RETURNING value) " - "SELECT value FROM poppedRows")); - } - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - statement->Execute(formatter.GetDictionary()); - - if (statement->IsDone()) - { - return false; - } - else - { - statement->SetResultFieldType(0, ValueType_BinaryString); - value = statement->ReadString(0); - return true; - } - } - } - uint64_t IndexBackend::GetQueueSize(DatabaseManager& manager, const std::string& queueId) { @@ -4636,6 +4582,87 @@ } #endif +#if ORTHANC_PLUGINS_HAS_RESERVE_QUEUE_VALUE == 1 + bool IndexBackend::ReserveQueueValue(std::string& value, + uint64_t& valueId, + DatabaseManager& manager, + const std::string& queueId, + bool fromFront, + uint32_t reserveTimeout) + { + LookupFormatter formatter(manager.GetDialect()); + + std::string queueIdParameter = formatter.GenerateParameter(queueId); + std::string reserveTimeoutParameter = formatter.GenerateParameter(reserveTimeout); + std::string nowParameter = formatter.GenerateParameter(GetSecondsSinceEpoch()); + + std::string minMax = (fromFront ? "MIN" : "MAX"); + std::string sql; + + switch (manager.GetDialect()) + { + case Dialect_PostgreSQL: + sql = "WITH RowToUpdate AS (SELECT " + minMax + "(id) FROM Queues WHERE queueId=" + queueIdParameter + " AND (reservedUntil IS NULL OR reservedUntil <= " + nowParameter + ")) " + " UPDATE Queues SET reservedUntil = " + nowParameter + " + " + reserveTimeoutParameter + " WHERE id IN (SELECT * FROM RowToUpdate) " + " RETURNING id, value;"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE_DYNAMIC(sql), manager, sql); + statement.Execute(formatter.GetDictionary()); + + if (statement.IsDone()) + { + return false; + } + else + { + statement.SetResultFieldType(0, ValueType_Integer64); + valueId = statement.ReadInteger64(0); + + statement.SetResultFieldType(1, ValueType_BinaryString); + value = statement.ReadString(1); + return true; + } + } + + void IndexBackend::AcknowledgeQueueValue(DatabaseManager& manager, + const std::string& queueId, + uint64_t valueId) + { + LookupFormatter formatter(manager.GetDialect()); + + std::unique_ptr<DatabaseManager::CachedStatement> statement; + + std::string queueIdParameter = formatter.GenerateParameter(queueId); + std::string valueIdParameter = formatter.GenerateParameter(valueId); + int64_t now = GetSecondsSinceEpoch(); + std::string nowParameter = formatter.GenerateParameter(now); + + switch (manager.GetDialect()) + { + case Dialect_PostgreSQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "DELETE FROM Queues WHERE queueId=" + queueIdParameter + " AND id=" + valueIdParameter + + " AND reservedUntil IS NOT NULL AND " + nowParameter + " < reservedUntil RETURNING id")); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + statement->Execute(formatter.GetDictionary()); + if (statement->IsDone()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "Unable to acknowledge a queue value. Has it expired ?"); + } + } +#endif + #if ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA == 1 void IndexBackend::GetAttachmentCustomData(std::string& customData, DatabaseManager& manager, @@ -4815,4 +4842,8 @@ } #endif + ISqlLookupFormatter* IndexBackend::CreateLookupFormatter(Dialect dialect) + { + return new LookupFormatter(dialect); + } }
--- a/Framework/Plugins/IndexBackend.h Wed Nov 26 14:20:16 2025 +0100 +++ b/Framework/Plugins/IndexBackend.h Fri Nov 28 15:38:54 2025 +0100 @@ -39,7 +39,9 @@ class IndexBackend : public IDatabaseBackend { private: +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 class LookupFormatter; +#endif OrthancPluginContext* context_; bool readOnly_; @@ -49,7 +51,6 @@ std::unique_ptr<IDatabaseBackendOutput::IFactory> outputFactory_; protected: - virtual void ClearDeletedFiles(DatabaseManager& manager); virtual void ClearDeletedResources(DatabaseManager& manager); @@ -84,13 +85,6 @@ const Dictionary& args, uint32_t limit); -#if ORTHANC_PLUGINS_HAS_QUEUES == 1 - bool DequeueValueSQLite(std::string& value, - DatabaseManager& manager, - const std::string& queueId, - bool fromFront); -#endif - public: explicit IndexBackend(OrthancPluginContext* context, bool readOnly, @@ -510,6 +504,19 @@ #endif +#if ORTHANC_PLUGINS_HAS_RESERVE_QUEUE_VALUE == 1 + virtual bool ReserveQueueValue(std::string& value, + uint64_t& valueId, + DatabaseManager& manager, + const std::string& queueId, + bool fromFront, + uint32_t reserveTimeout) ORTHANC_OVERRIDE; + + virtual void AcknowledgeQueueValue(DatabaseManager& manager, + const std::string& queueId, + uint64_t valueId) ORTHANC_OVERRIDE; +#endif + #if ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA == 1 virtual void GetAttachmentCustomData(std::string& customData, DatabaseManager& manager, @@ -518,7 +525,6 @@ virtual void SetAttachmentCustomData(DatabaseManager& manager, const std::string& attachmentUuid, const std::string& customData) ORTHANC_OVERRIDE; - #endif #if ORTHANC_PLUGINS_HAS_AUDIT_LOGS == 1 @@ -569,5 +575,9 @@ static DatabaseManager* CreateSingleDatabaseManager(IDatabaseBackend& backend, bool hasIdentifierTags, const std::list<IdentifierTag>& identifierTags); + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + ISqlLookupFormatter* CreateLookupFormatter(Dialect dialect); +#endif }; }
--- a/Framework/Plugins/IndexUnitTests.h Wed Nov 26 14:20:16 2025 +0100 +++ b/Framework/Plugins/IndexUnitTests.h Fri Nov 28 15:38:54 2025 +0100 @@ -28,6 +28,7 @@ #include "GlobalProperties.h" #include <Compatibility.h> // For std::unique_ptr<> +#include <SystemToolbox.h> #include <gtest/gtest.h> #include <list> @@ -1065,5 +1066,69 @@ } #endif +#if ORTHANC_PLUGINS_HAS_RESERVE_QUEUE_VALUE == 1 + { + std::string value; + uint64_t valueIdA, valueIdB, valueIdC, valueIdD, valueIdE, valueIdFail; + + { + manager->StartTransaction(TransactionType_ReadWrite); + + db.EnqueueValue(*manager, "test", "a"); + db.EnqueueValue(*manager, "test", "b"); + db.EnqueueValue(*manager, "test", "c"); + db.EnqueueValue(*manager, "test", "d"); + db.EnqueueValue(*manager, "test", "e"); + + ASSERT_EQ(5u, db.GetQueueSize(*manager, "test")); + + ASSERT_TRUE(db.ReserveQueueValue(value, valueIdA, *manager, "test", true, 1000)); + ASSERT_EQ("a", value); + ASSERT_TRUE(db.ReserveQueueValue(value, valueIdB, *manager, "test", true, 1)); + ASSERT_EQ("b", value); + ASSERT_TRUE(db.ReserveQueueValue(value, valueIdE, *manager, "test", false, 1)); + ASSERT_EQ("e", value); + ASSERT_TRUE(db.ReserveQueueValue(value, valueIdD, *manager, "test", false, 1)); + ASSERT_EQ("d", value); + manager->CommitTransaction(); + } + + { + manager->StartTransaction(TransactionType_ReadWrite); + + db.AcknowledgeQueueValue(*manager, "test", valueIdA); + db.AcknowledgeQueueValue(*manager, "test", valueIdE); + manager->CommitTransaction(); + } + + Orthanc::SystemToolbox::USleep(2000000); // Wait 2 seconds -> b and d should be released + + { + manager->StartTransaction(TransactionType_ReadWrite); + ASSERT_TRUE(db.ReserveQueueValue(value, valueIdB, *manager, "test", true, 1)); + ASSERT_EQ("b", value); + ASSERT_TRUE(db.ReserveQueueValue(value, valueIdD, *manager, "test", false, 1)); + ASSERT_EQ("d", value); + ASSERT_TRUE(db.ReserveQueueValue(value, valueIdC, *manager, "test", false, 1)); + ASSERT_EQ("c", value); + ASSERT_FALSE(db.ReserveQueueValue(value, valueIdFail, *manager, "test", false, 1)); + + manager->CommitTransaction(); + } + + Orthanc::SystemToolbox::USleep(2000000); // Wait 2 seconds -> b, c and d should be released + + // try to acknowledge a value after it has expired + { + manager->StartTransaction(TransactionType_ReadWrite); + + ASSERT_THROW(db.AcknowledgeQueueValue(*manager, "test", valueIdC), Orthanc::OrthancException); + + manager->CommitTransaction(); + } + + } +#endif + manager->Close(); }
--- a/Framework/Plugins/MessagesToolbox.h Wed Nov 26 14:20:16 2025 +0100 +++ b/Framework/Plugins/MessagesToolbox.h Fri Nov 28 15:38:54 2025 +0100 @@ -54,6 +54,7 @@ #define ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA 0 #define ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES 0 #define ORTHANC_PLUGINS_HAS_QUEUES 0 +#define ORTHANC_PLUGINS_HAS_RESERVE_QUEUE_VALUE 0 #define ORTHANC_PLUGINS_HAS_AUDIT_LOGS 0 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) @@ -75,6 +76,13 @@ # endif #endif +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 10) +# undef ORTHANC_PLUGINS_HAS_RESERVE_QUEUE_VALUE +# define ORTHANC_PLUGINS_HAS_RESERVE_QUEUE_VALUE 1 +# endif +#endif + #include <Enumerations.h>
--- a/Framework/PostgreSQL/PostgreSQLIncludes.h Wed Nov 26 14:20:16 2025 +0100 +++ b/Framework/PostgreSQL/PostgreSQLIncludes.h Fri Nov 28 15:38:54 2025 +0100 @@ -44,12 +44,19 @@ # error PG_VERSION_NUM is not defined #endif -#if PG_VERSION_NUM >= 110000 -# include <catalog/pg_type_d.h> + + +#if PG_VERSION_NUM < 180000 +# if PG_VERSION_NUM >= 110000 +# include <catalog/pg_type_d.h> +# else +# include <postgres.h> +# undef LOG // This one comes from <postgres.h>, and conflicts with <Core/Logging.h> +# include <catalog/pg_type.h> +# endif #else -# include <postgres.h> -# undef LOG // This one comes from <postgres.h>, and conflicts with <Core/Logging.h> -# include <catalog/pg_type.h> +// from libpq 18, we avoid using server headers to simplify the "configure steps" +# include "PostgreSQLOids.h" #endif #include <libpq-fe.h>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/PostgreSQL/PostgreSQLOids.h Fri Nov 28 15:38:54 2025 +0100 @@ -0,0 +1,47 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2025 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if PG_VERSION_NUM < 180000 +# error This file shall not be included if you are linking against libpq < 18 +#endif +// Object ID type in PostgreSQL +typedef unsigned int Oid; + +// Core built-in type OIDs. +// All these OIDs are guaranteed not to change. +// By defining them here, we avoid including server only headers +#define BOOLOID 16 +#define BYTEAOID 17 +#define CHAROID 18 +#define NAMEOID 19 +#define INT8OID 20 +#define INT2OID 21 +#define INT4OID 23 +#define TEXTOID 25 +#define OIDOID 26 +#define VARCHAROID 1043 +#define TIMESTAMPOID 1114 +#define TIMESTAMPTZOID 1184 +#define VOIDOID 2278 \ No newline at end of file
--- a/MySQL/Plugins/MySQLIndex.h Wed Nov 26 14:20:16 2025 +0100 +++ b/MySQL/Plugins/MySQLIndex.h Fri Nov 28 15:38:54 2025 +0100 @@ -76,6 +76,11 @@ return false; } + virtual bool HasReserveQueueValue() const ORTHANC_OVERRIDE + { + return false; + } + virtual bool HasAuditLogs() const ORTHANC_OVERRIDE { return false;
--- a/Odbc/Plugins/OdbcIndex.h Wed Nov 26 14:20:16 2025 +0100 +++ b/Odbc/Plugins/OdbcIndex.h Fri Nov 28 15:38:54 2025 +0100 @@ -88,6 +88,11 @@ return false; } + virtual bool HasReserveQueueValue() const ORTHANC_OVERRIDE + { + return false; + } + virtual bool HasAuditLogs() const ORTHANC_OVERRIDE { return false;
--- a/PostgreSQL/CMakeLists.txt Wed Nov 26 14:20:16 2025 +0100 +++ b/PostgreSQL/CMakeLists.txt Fri Nov 28 15:38:54 2025 +0100 @@ -95,7 +95,8 @@ POSTGRESQL_UPGRADE_REV2_TO_REV3 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev2ToRev3.sql POSTGRESQL_UPGRADE_REV3_TO_REV4 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev3ToRev4.sql POSTGRESQL_UPGRADE_REV4_TO_REV5 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev4ToRev5.sql - POSTGRESQL_UPGRADE_REV5_TO_REV6 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev5ToRev6.sql + POSTGRESQL_UPGRADE_REV5_TO_REV6 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev5ToRev6.sql + POSTGRESQL_UPGRADE_REV6_TO_REV10 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev6ToRev10.sql )
--- a/PostgreSQL/NEWS Wed Nov 26 14:20:16 2025 +0100 +++ b/PostgreSQL/NEWS Fri Nov 28 15:38:54 2025 +0100 @@ -1,15 +1,17 @@ Pending changes in the mainline =============================== -DB schema revision: 6 +DB schema revision: 10 Minimum plugin SDK (for build): 1.12.5 -Optimal plugin SDK (for build): 1.12.9 +Optimal plugin SDK (for build): 1.12.10 (TODO: update once released !) Minimum Orthanc runtime: 1.12.5 -Optimal Orthanc runtime: 1.12.9 +Optimal Orthanc runtime: 1.12.10 Minimal Postgresql Server version: 9 Optimal Postgresql Server version: 11+ +TODO before release: update SDK to 1.12.10 + Changes: * New configuration "Schema" (default value: 'public') to allow Orthanc to use another schema. Note that, if you are not using the default 'public' @@ -23,11 +25,14 @@ * New configuration "ApplicationName" (default value is empty) that is copied in the application_name argument in the connection string. This name is used to identify the origin of queries in statistics and logs in the PostgreSQL server. - +* SDK: Added support for ReserveQueueValue and AcknowledgeQueueValue (new in SDK 1.12.10) Maintenance: -* Now verifying the DatabasePatchLevel (revision) in another transaction than - the one that upgrades the schema. +* Added a new primary key column in the InvalidChildCounts and GlobalIntegersChanges + tables. This new column is required for pg_repack to be able to reclaim space on + these tables. +* Upgraded dependencies for static builds (notably on Windows and LSB): + - libpq 18.1 (replacing libpq 13.1 - except for macOS universal binaries that still uses 13.1) Release 9.0 (2025-08-13)
--- a/PostgreSQL/Plugins/PostgreSQLIndex.cpp Wed Nov 26 14:20:16 2025 +0100 +++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp Fri Nov 28 15:38:54 2025 +0100 @@ -49,7 +49,7 @@ static const GlobalProperty GlobalProperty_HasComputeStatisticsReadOnly = GlobalProperty_DatabaseInternal4; } -#define CURRENT_DB_REVISION 6 +#define CURRENT_DB_REVISION 10 namespace OrthancDatabases { @@ -268,6 +268,19 @@ currentRevision = 6; } + if (currentRevision == 6) + { + LOG(WARNING) << "Upgrading DB schema from revision 6 to revision 10 (there are no versions 7, 8 and 9 !)"; + + std::string query; + + Orthanc::EmbeddedResources::GetFileResource + (query, Orthanc::EmbeddedResources::POSTGRESQL_UPGRADE_REV6_TO_REV10); + t.GetDatabaseTransaction().ExecuteMultiLines(query); + hasAppliedAnUpgrade = true; + currentRevision = 10; + } + if (hasAppliedAnUpgrade) { LOG(WARNING) << "Upgrading DB schema by applying PrepareIndex.sql";
--- a/PostgreSQL/Plugins/PostgreSQLIndex.h Wed Nov 26 14:20:16 2025 +0100 +++ b/PostgreSQL/Plugins/PostgreSQLIndex.h Fri Nov 28 15:38:54 2025 +0100 @@ -87,6 +87,11 @@ return true; } + virtual bool HasReserveQueueValue() const ORTHANC_OVERRIDE + { + return true; + } + virtual bool HasAuditLogs() const ORTHANC_OVERRIDE { return true;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PostgreSQL/Plugins/SQL/Downgrades/Rev699ToRev6.sql Fri Nov 28 15:38:54 2025 +0100 @@ -0,0 +1,8 @@ +ALTER TABLE InvalidChildCounts DROP COLUMN pk; +ALTER TABLE GlobalIntegersChanges DROP COLUMN pk; +---------- + +-- set the global properties that actually documents the DB version, revision and some of the capabilities +-- modify only the ones that have changed +DELETE FROM GlobalProperties WHERE property IN (4); +INSERT INTO GlobalProperties VALUES (4, 6); -- GlobalProperty_DatabasePatchLevel
--- a/PostgreSQL/Plugins/SQL/PrepareIndex.sql Wed Nov 26 14:20:16 2025 +0100 +++ b/PostgreSQL/Plugins/SQL/PrepareIndex.sql Fri Nov 28 15:38:54 2025 +0100 @@ -400,6 +400,7 @@ -- These changes will be applied at regular interval by an external thread or when someone -- requests the statistics CREATE TABLE IF NOT EXISTS GlobalIntegersChanges( + pk BIGSERIAL PRIMARY KEY, -- new in rev10 required for pg_repack to be able to reclaim space key INTEGER, value BIGINT); @@ -458,7 +459,7 @@ CREATE OR REPLACE FUNCTION IncrementResourcesTrackerFunc() RETURNS TRIGGER AS $$ BEGIN - INSERT INTO GlobalIntegersChanges VALUES(new.resourceType + 2, 1); + INSERT INTO GlobalIntegersChanges (key, value) VALUES(new.resourceType + 2, 1); RETURN NULL; END; $$ LANGUAGE plpgsql; @@ -466,7 +467,7 @@ CREATE OR REPLACE FUNCTION DecrementResourcesTrackerFunc() RETURNS TRIGGER AS $$ BEGIN - INSERT INTO GlobalIntegersChanges VALUES(old.resourceType + 2, -1); + INSERT INTO GlobalIntegersChanges (key, value) VALUES(old.resourceType + 2, -1); RETURN NULL; END; $$ LANGUAGE plpgsql; @@ -475,8 +476,8 @@ CREATE OR REPLACE FUNCTION AttachedFileIncrementSizeFunc() RETURNS TRIGGER AS $body$ BEGIN - INSERT INTO GlobalIntegersChanges VALUES(0, new.compressedSize); - INSERT INTO GlobalIntegersChanges VALUES(1, new.uncompressedSize); + INSERT INTO GlobalIntegersChanges (key, value) VALUES(0, new.compressedSize); + INSERT INTO GlobalIntegersChanges (key, value) VALUES(1, new.uncompressedSize); RETURN NULL; END; $body$ LANGUAGE plpgsql; @@ -484,8 +485,8 @@ CREATE OR REPLACE FUNCTION AttachedFileDecrementSizeFunc() RETURNS TRIGGER AS $body$ BEGIN - INSERT INTO GlobalIntegersChanges VALUES(0, -old.compressedSize); - INSERT INTO GlobalIntegersChanges VALUES(1, -old.uncompressedSize); + INSERT INTO GlobalIntegersChanges (key, value) VALUES(0, -old.compressedSize); + INSERT INTO GlobalIntegersChanges (key, value) VALUES(1, -old.uncompressedSize); RETURN NULL; END; $body$ LANGUAGE plpgsql; @@ -704,6 +705,7 @@ -- At regular interval, the DB housekeeping thread updates the childCount column of -- resources with an entry in this table. CREATE TABLE IF NOT EXISTS InvalidChildCounts( + pk BIGSERIAL PRIMARY KEY, -- new in rev10 required for pg_repack to be able to reclaim space id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, updatedAt TIMESTAMP DEFAULT NOW()); @@ -756,7 +758,7 @@ IF TG_OP = 'INSERT' THEN IF new.parentId IS NOT NULL THEN -- mark the parent's childCount as invalid - INSERT INTO InvalidChildCounts VALUES(new.parentId); + INSERT INTO InvalidChildCounts (id) VALUES(new.parentId); END IF; ELSIF TG_OP = 'DELETE' THEN @@ -764,7 +766,7 @@ IF old.parentId IS NOT NULL THEN BEGIN -- mark the parent's childCount as invalid - INSERT INTO InvalidChildCounts VALUES(old.parentId); + INSERT INTO InvalidChildCounts (id) VALUES(old.parentId); EXCEPTION -- when deleting the last child of a parent, the insert will fail (this is expected) WHEN foreign_key_violation THEN NULL; @@ -800,7 +802,8 @@ CREATE TABLE IF NOT EXISTS Queues ( id BIGSERIAL NOT NULL PRIMARY KEY, queueId TEXT NOT NULL, - value BYTEA NOT NULL + value BYTEA NOT NULL, + reservedUntil BIGINT DEFAULT NULL -- new in rev 10 ); CREATE INDEX IF NOT EXISTS QueuesIndex ON Queues (queueId, id); @@ -854,7 +857,7 @@ -- set the global properties that actually documents the DB version, revision and some of the capabilities DELETE FROM GlobalProperties WHERE property IN (1, 4, 6, 10, 11, 12, 13, 14); INSERT INTO GlobalProperties VALUES (1, 6); -- GlobalProperty_DatabaseSchemaVersion -INSERT INTO GlobalProperties VALUES (4, 6); -- GlobalProperty_DatabasePatchLevel +INSERT INTO GlobalProperties VALUES (4, 10); -- GlobalProperty_DatabasePatchLevel INSERT INTO GlobalProperties VALUES (6, 1); -- GlobalProperty_GetTotalSizeIsFast INSERT INTO GlobalProperties VALUES (10, 1); -- GlobalProperty_HasTrigramIndex INSERT INTO GlobalProperties VALUES (11, 3); -- GlobalProperty_HasCreateInstance -- this is actually the 3rd version of HasCreateInstance
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PostgreSQL/Plugins/SQL/Upgrades/Rev6ToRev10.sql Fri Nov 28 15:38:54 2025 +0100 @@ -0,0 +1,7 @@ +-- Adding a PK to these 2 table to allow pg_repack to process these tables, enabling reclaiming disk space and defragmenting the tables. + +ALTER TABLE InvalidChildCounts ADD COLUMN pk BIGSERIAL PRIMARY KEY; +ALTER TABLE GlobalIntegersChanges ADD COLUMN pk BIGSERIAL PRIMARY KEY; + +-- Adding the queues timeout +ALTER TABLE Queues ADD COLUMN reservedUntil BIGINT DEFAULT NULL; \ No newline at end of file
--- a/PostgreSQL/UnitTests/UnitTestsMain.cpp Wed Nov 26 14:20:16 2025 +0100 +++ b/PostgreSQL/UnitTests/UnitTestsMain.cpp Fri Nov 28 15:38:54 2025 +0100 @@ -38,7 +38,7 @@ TEST(PostgreSQL, Version) { - ASSERT_STREQ("13.1", PG_VERSION); + ASSERT_STREQ("18.1", PG_VERSION); } #endif
--- a/Resources/CMake/PostgreSQLConfiguration.cmake Wed Nov 26 14:20:16 2025 +0100 +++ b/Resources/CMake/PostgreSQLConfiguration.cmake Fri Nov 28 15:38:54 2025 +0100 @@ -48,13 +48,21 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPQ) add_definitions(-DORTHANC_POSTGRESQL_STATIC=1) - SET(LIBPQ_MAJOR 13) - SET(LIBPQ_MINOR 1) + if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + SET(LIBPQ_MAJOR 13) + SET(LIBPQ_MINOR 23) + SET(LIBPQ_MD5SUM 86f7b1ace0dc43e993f29a6739a264d8) + else() + SET(LIBPQ_MAJOR 18) + SET(LIBPQ_MINOR 1) + SET(LIBPQ_MD5SUM 523b5e7f7f64d331004fd93d37109aa0) + endif() + SET(LIBPQ_VERSION ${LIBPQ_MAJOR}.${LIBPQ_MINOR}) SET(LIBPQ_SOURCES_DIR ${CMAKE_BINARY_DIR}/postgresql-${LIBPQ_VERSION}) DownloadPackage( - "551302a823a1ab48b4ed14166beebba9" + "${LIBPQ_MD5SUM}" "https://orthanc.uclouvain.be/downloads/third-party-downloads/postgresql-${LIBPQ_VERSION}.tar.gz" "${LIBPQ_SOURCES_DIR}") @@ -103,11 +111,15 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_definitions( - -D_GNU_SOURCE -D_THREAD_SAFE -D_POSIX_PTHREAD_SEMANTICS ) + # this has been included in the OrthancFramework from 1.12.10+ + if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSTRERROR_R_INT=1 -D_POSIX_C_SOURCE=200112L") + endif() + configure_file( ${LIBPQ_SOURCES_DIR}/src/include/port/darwin.h ${AUTOGENERATED_DIR}/pg_config_os.h @@ -181,6 +193,8 @@ set(PG_INT64_TYPE "long int") endif() + check_type_size("long long" SIZEOF_LONG_LONG) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(ALIGNOF_DOUBLE 8) set(ALIGNOF_INT 4) @@ -307,23 +321,24 @@ set(USE_OPENSSL 1) set(USE_OPENSSL_RANDOM 1) - PrepareCMakeConfigurationFile( - ${LIBPQ_SOURCES_DIR}/src/include/pg_config_ext.h.in - ${AUTOGENERATED_DIR}/pg_config_ext.h.in) - + PrepareCMakeConfigurationFile( ${LIBPQ_SOURCES_DIR}/src/include/pg_config.h.in ${AUTOGENERATED_DIR}/pg_config.h.in) configure_file( - ${AUTOGENERATED_DIR}/pg_config_ext.h.in - ${AUTOGENERATED_DIR}/pg_config_ext.h) - - configure_file( ${AUTOGENERATED_DIR}/pg_config.h.in ${AUTOGENERATED_DIR}/pg_config.h) - + if (LIBPQ_MAJOR STREQUAL "13") + PrepareCMakeConfigurationFile( + ${LIBPQ_SOURCES_DIR}/src/include/pg_config_ext.h.in + ${AUTOGENERATED_DIR}/pg_config_ext.h.in) + + configure_file( + ${AUTOGENERATED_DIR}/pg_config_ext.h.in + ${AUTOGENERATED_DIR}/pg_config_ext.h) + endif() ## ## Generic configuration @@ -350,45 +365,96 @@ ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq ) - set(LIBPQ_SOURCES - # Don't use files from the "src/backend/" folder - ${LIBPQ_SOURCES_DIR}/src/common/base64.c - ${LIBPQ_SOURCES_DIR}/src/common/encnames.c - ${LIBPQ_SOURCES_DIR}/src/common/ip.c - ${LIBPQ_SOURCES_DIR}/src/common/link-canary.c - ${LIBPQ_SOURCES_DIR}/src/common/md5.c - ${LIBPQ_SOURCES_DIR}/src/common/saslprep.c - ${LIBPQ_SOURCES_DIR}/src/common/scram-common.c - ${LIBPQ_SOURCES_DIR}/src/common/sha2_openssl.c - ${LIBPQ_SOURCES_DIR}/src/common/string.c - ${LIBPQ_SOURCES_DIR}/src/common/unicode_norm.c - ${LIBPQ_SOURCES_DIR}/src/common/wchar.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-auth-scram.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-auth.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-connect.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-exec.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-lobj.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-misc.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-print.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-protocol2.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-protocol3.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure-common.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure-openssl.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/libpq-events.c - ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/pqexpbuffer.c - ${LIBPQ_SOURCES_DIR}/src/port/chklocale.c - ${LIBPQ_SOURCES_DIR}/src/port/explicit_bzero.c - ${LIBPQ_SOURCES_DIR}/src/port/getaddrinfo.c - ${LIBPQ_SOURCES_DIR}/src/port/inet_net_ntop.c - ${LIBPQ_SOURCES_DIR}/src/port/noblock.c - ${LIBPQ_SOURCES_DIR}/src/port/pg_strong_random.c - ${LIBPQ_SOURCES_DIR}/src/port/pgstrcasecmp.c - ${LIBPQ_SOURCES_DIR}/src/port/pqsignal.c - ${LIBPQ_SOURCES_DIR}/src/port/snprintf.c - ${LIBPQ_SOURCES_DIR}/src/port/strerror.c - ${LIBPQ_SOURCES_DIR}/src/port/thread.c - ) + if (LIBPQ_MAJOR STREQUAL "18") + + set(LIBPQ_SOURCES + # Don't use files from the "src/backend/" folder + ${LIBPQ_SOURCES_DIR}/src/common/base64.c + ${LIBPQ_SOURCES_DIR}/src/common/cryptohash_openssl.c + ${LIBPQ_SOURCES_DIR}/src/common/encnames.c + ${LIBPQ_SOURCES_DIR}/src/common/fe_memutils.c + ${LIBPQ_SOURCES_DIR}/src/common/ip.c + ${LIBPQ_SOURCES_DIR}/src/common/link-canary.c + ${LIBPQ_SOURCES_DIR}/src/common/jsonapi.c + ${LIBPQ_SOURCES_DIR}/src/common/md5.c + ${LIBPQ_SOURCES_DIR}/src/common/md5_common.c + ${LIBPQ_SOURCES_DIR}/src/common/pg_prng.c + ${LIBPQ_SOURCES_DIR}/src/common/psprintf.c + ${LIBPQ_SOURCES_DIR}/src/common/saslprep.c + ${LIBPQ_SOURCES_DIR}/src/common/scram-common.c + ${LIBPQ_SOURCES_DIR}/src/common/hmac_openssl.c + ${LIBPQ_SOURCES_DIR}/src/common/string.c + ${LIBPQ_SOURCES_DIR}/src/common/stringinfo.c + ${LIBPQ_SOURCES_DIR}/src/common/unicode_norm.c + ${LIBPQ_SOURCES_DIR}/src/common/wchar.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-auth-oauth.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-auth-scram.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-auth.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-cancel.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-connect.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-exec.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-lobj.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-misc.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-print.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-protocol3.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure-common.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure-openssl.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-trace.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/libpq-events.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/pqexpbuffer.c + ${LIBPQ_SOURCES_DIR}/src/port/chklocale.c + ${LIBPQ_SOURCES_DIR}/src/port/explicit_bzero.c + ${LIBPQ_SOURCES_DIR}/src/port/inet_net_ntop.c + ${LIBPQ_SOURCES_DIR}/src/port/noblock.c + ${LIBPQ_SOURCES_DIR}/src/port/pg_strong_random.c + ${LIBPQ_SOURCES_DIR}/src/port/pg_bitutils.c + ${LIBPQ_SOURCES_DIR}/src/port/pgstrcasecmp.c + ${LIBPQ_SOURCES_DIR}/src/port/pqsignal.c + ${LIBPQ_SOURCES_DIR}/src/port/snprintf.c + ${LIBPQ_SOURCES_DIR}/src/port/strerror.c + ) + else() + set(LIBPQ_SOURCES + # Don't use files from the "src/backend/" folder + ${LIBPQ_SOURCES_DIR}/src/common/base64.c + ${LIBPQ_SOURCES_DIR}/src/common/encnames.c + ${LIBPQ_SOURCES_DIR}/src/common/ip.c + ${LIBPQ_SOURCES_DIR}/src/common/link-canary.c + ${LIBPQ_SOURCES_DIR}/src/common/md5.c + ${LIBPQ_SOURCES_DIR}/src/common/saslprep.c + ${LIBPQ_SOURCES_DIR}/src/common/scram-common.c + ${LIBPQ_SOURCES_DIR}/src/common/sha2_openssl.c + ${LIBPQ_SOURCES_DIR}/src/common/string.c + ${LIBPQ_SOURCES_DIR}/src/common/unicode_norm.c + ${LIBPQ_SOURCES_DIR}/src/common/wchar.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-auth-scram.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-auth.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-connect.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-exec.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-lobj.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-misc.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-print.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-protocol2.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-protocol3.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure-common.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure-openssl.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/libpq-events.c + ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/pqexpbuffer.c + ${LIBPQ_SOURCES_DIR}/src/port/chklocale.c + ${LIBPQ_SOURCES_DIR}/src/port/explicit_bzero.c + ${LIBPQ_SOURCES_DIR}/src/port/getaddrinfo.c + ${LIBPQ_SOURCES_DIR}/src/port/inet_net_ntop.c + ${LIBPQ_SOURCES_DIR}/src/port/noblock.c + ${LIBPQ_SOURCES_DIR}/src/port/pg_strong_random.c + ${LIBPQ_SOURCES_DIR}/src/port/pgstrcasecmp.c + ${LIBPQ_SOURCES_DIR}/src/port/pqsignal.c + ${LIBPQ_SOURCES_DIR}/src/port/snprintf.c + ${LIBPQ_SOURCES_DIR}/src/port/strerror.c + ${LIBPQ_SOURCES_DIR}/src/port/thread.c + ) + endif() if (NOT HAVE_STRLCPY) LIST(APPEND LIBPQ_SOURCES
--- a/SQLite/Plugins/SQLiteIndex.cpp Wed Nov 26 14:20:16 2025 +0100 +++ b/SQLite/Plugins/SQLiteIndex.cpp Fri Nov 28 15:38:54 2025 +0100 @@ -299,4 +299,61 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } #endif + +#if ORTHANC_PLUGINS_HAS_QUEUES == 1 + bool SQLiteIndex::DequeueValue(std::string& value, + DatabaseManager& manager, + const std::string& queueId, + bool fromFront) + { + assert(manager.GetDialect() == Dialect_SQLite); + + std::unique_ptr<ISqlLookupFormatter> formatter(CreateLookupFormatter(manager.GetDialect())); + + std::unique_ptr<DatabaseManager::CachedStatement> statement; + + std::string queueIdParameter = formatter->GenerateParameter(queueId); + + if (fromFront) + { + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "SELECT id, value FROM Queues WHERE queueId=" + queueIdParameter + " ORDER BY id ASC LIMIT 1")); + } + else + { + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "SELECT id, value FROM Queues WHERE queueId=" + queueIdParameter + " ORDER BY id DESC LIMIT 1")); + } + + statement->Execute(formatter->GetDictionary()); + + if (statement->IsDone()) + { + return false; + } + else + { + statement->SetResultFieldType(0, ValueType_Integer64); + statement->SetResultFieldType(1, ValueType_BinaryString); + + value = statement->ReadString(1); + + { + DatabaseManager::CachedStatement s2(STATEMENT_FROM_HERE, manager, + "DELETE FROM Queues WHERE id=${id}"); + + s2.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", statement->ReadInteger64(0)); + + s2.Execute(args); + } + + return true; + } + } +#endif }
--- a/SQLite/Plugins/SQLiteIndex.h Wed Nov 26 14:20:16 2025 +0100 +++ b/SQLite/Plugins/SQLiteIndex.h Fri Nov 28 15:38:54 2025 +0100 @@ -70,6 +70,11 @@ return true; } + virtual bool HasReserveQueueValue() const ORTHANC_OVERRIDE + { + return false; + } + virtual bool HasAuditLogs() const ORTHANC_OVERRIDE { return false; @@ -108,5 +113,12 @@ { return false; } + +#if ORTHANC_PLUGINS_HAS_QUEUES == 1 + virtual bool DequeueValue(std::string& value, + DatabaseManager& manager, + const std::string& queueId, + bool fromFront) ORTHANC_OVERRIDE; +#endif }; }
--- a/TODO Wed Nov 26 14:20:16 2025 +0100 +++ b/TODO Fri Nov 28 15:38:54 2025 +0100 @@ -7,10 +7,6 @@ Common - Database index ----------------------- -* Try to avoid the use of temporary tables: - https://discourse.orthanc-server.org/t/image-insert-are-too-slow-databse-performance-too-poor/3820 - - * Implement "large queries" for: - updating all metadata of a resource at once - update all maindicomtags of 4 resource levels at once @@ -31,19 +27,7 @@ PostgreSQL ---------- -* Check if we can force the schema that is used. By default, Orthanc - is using the 'public' schema but, after a wrong command, we have seen - a DB where there was a 'AttachedFiles' table in the public schema and another one in a - 'MyPacs' schema and Orthanc was actually using the 'MyPacs.AttachedFiles' table !!! - Orthanc was then seeing only the most recent attached files !!! - We should also be able to use other schemas: - https://discourse.orthanc-server.org/t/orthanc-container-unable-to-connect-to-specified-postgresql-database/5471 - - -* Seems Orthanc might deadlock when there are plenty of conflicting transactions: - https://groups.google.com/g/orthanc-users/c/xQelEcKqL9U/m/HsvxwlkvAQAJ - https://groups.google.com/g/orthanc-users/c/1bkClfZ0KBA/m/s4AlwVh3CQAJ - +* upgrade to libpq 18.1 ----- MySQL
