Mercurial > hg > orthanc-databases
changeset 674:fc78f08ee019 attach-custom-data
many fixes, added unit tests
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 30 May 2025 21:42:13 +0200 (2 months ago) |
parents | 3d1faa34233f |
children | 0d2ccb51c70b |
files | Framework/Common/BinaryStringValue.h Framework/Plugins/DatabaseBackendAdapterV4.cpp Framework/Plugins/IDatabaseBackend.h Framework/Plugins/IndexBackend.cpp Framework/Plugins/IndexBackend.h Framework/Plugins/IndexUnitTests.h Framework/PostgreSQL/PostgreSQLResult.cpp Framework/PostgreSQL/PostgreSQLResult.h Framework/PostgreSQL/PostgreSQLStatement.cpp PostgreSQL/Plugins/SQL/PrepareIndex.sql SQLite/Plugins/PrepareIndex.sql |
diffstat | 11 files changed, 372 insertions(+), 94 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Common/BinaryStringValue.h Fri May 30 15:46:56 2025 +0200 +++ b/Framework/Common/BinaryStringValue.h Fri May 30 21:42:13 2025 +0200 @@ -35,6 +35,10 @@ std::string content_; public: + BinaryStringValue() + { + } + explicit BinaryStringValue(const std::string& content) : content_(content) { @@ -55,6 +59,11 @@ return content_.size(); } + void Swap(std::string& other) + { + content_.swap(other); + } + virtual ValueType GetType() const ORTHANC_OVERRIDE { return ValueType_BinaryString;
--- a/Framework/Plugins/DatabaseBackendAdapterV4.cpp Fri May 30 15:46:56 2025 +0200 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp Fri May 30 21:42:13 2025 +0200 @@ -1370,8 +1370,7 @@ case Orthanc::DatabasePluginMessages::OPERATION_GET_KEY_VALUE: { std::string value; - bool found = backend.GetKeyValue(manager, - value, + bool found = backend.GetKeyValue(value, manager, request.get_key_value().store_id(), request.get_key_value().key()); response.mutable_get_key_value()->set_found(found); @@ -1409,8 +1408,7 @@ case Orthanc::DatabasePluginMessages::OPERATION_DEQUEUE_VALUE: { std::string value; - bool found = backend.DequeueValue(manager, - value, + bool found = backend.DequeueValue(value, manager, request.dequeue_value().queue_id(), request.dequeue_value().origin() == Orthanc::DatabasePluginMessages::QUEUE_ORIGIN_FRONT); response.mutable_dequeue_value()->set_found(found);
--- a/Framework/Plugins/IDatabaseBackend.h Fri May 30 15:46:56 2025 +0200 +++ b/Framework/Plugins/IDatabaseBackend.h Fri May 30 21:42:13 2025 +0200 @@ -415,18 +415,18 @@ #if ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES == 1 virtual void StoreKeyValue(DatabaseManager& manager, - const std::string& storeId, - const std::string& key, - const std::string& value) = 0; + const std::string& storeId, + const std::string& key, + const std::string& value) = 0; virtual void DeleteKeyValue(DatabaseManager& manager, const std::string& storeId, const std::string& key) = 0; - virtual bool GetKeyValue(DatabaseManager& manager, - std::string& value, - const std::string& storeId, - const std::string& key) = 0; + virtual bool GetKeyValue(std::string& value, + DatabaseManager& manager, + const std::string& storeId, + const std::string& key) = 0; virtual void ListKeysValues(Orthanc::DatabasePluginMessages::TransactionResponse& response, DatabaseManager& manager, @@ -438,8 +438,8 @@ const std::string& queueId, const std::string& value) = 0; - virtual bool DequeueValue(DatabaseManager& manager, - std::string& value, + virtual bool DequeueValue(std::string& value, + DatabaseManager& manager, const std::string& queueId, bool fromFront) = 0;
--- a/Framework/Plugins/IndexBackend.cpp Fri May 30 15:46:56 2025 +0200 +++ b/Framework/Plugins/IndexBackend.cpp Fri May 30 21:42:13 2025 +0200 @@ -23,8 +23,6 @@ #include "IndexBackend.h" -#include "../Common/BinaryStringValue.h" -#include "../Common/Integer64Value.h" #include "../Common/Utf8StringValue.h" #include "DatabaseBackendAdapterV2.h" #include "DatabaseBackendAdapterV3.h" @@ -4385,15 +4383,14 @@ STATEMENT_FROM_HERE, manager, "INSERT INTO KeyValueStores VALUES(${storeId}, ${key}, ${value}) ON CONFLICT (storeId, key) DO UPDATE SET value = EXCLUDED.value;"); - Dictionary args; - statement.SetParameterType("storeId", ValueType_Utf8String); statement.SetParameterType("key", ValueType_Utf8String); - statement.SetParameterType("value", ValueType_Utf8String); - + statement.SetParameterType("value", ValueType_BinaryString); + + Dictionary args; args.SetUtf8Value("storeId", storeId); args.SetUtf8Value("key", key); - args.SetUtf8Value("value", value); + args.SetBinaryValue("value", value); statement.Execute(args); } @@ -4406,36 +4403,35 @@ STATEMENT_FROM_HERE, manager, "DELETE FROM KeyValueStores WHERE storeId = ${storeId} AND key = ${key}"); - Dictionary args; - statement.SetParameterType("storeId", ValueType_Utf8String); statement.SetParameterType("key", ValueType_Utf8String); + Dictionary args; args.SetUtf8Value("storeId", storeId); args.SetUtf8Value("key", key); statement.Execute(args); } - bool IndexBackend::GetKeyValue(DatabaseManager& manager, - std::string& value, + bool IndexBackend::GetKeyValue(std::string& value, + DatabaseManager& manager, const std::string& storeId, - const std::string& key) + const std::string& key) { DatabaseManager::CachedStatement statement( STATEMENT_FROM_HERE, manager, "SELECT value FROM KeyValueStores WHERE storeId = ${storeId} AND key = ${key}"); statement.SetReadOnly(true); - - Dictionary args; statement.SetParameterType("storeId", ValueType_Utf8String); statement.SetParameterType("key", ValueType_Utf8String); + Dictionary args; args.SetUtf8Value("storeId", storeId); args.SetUtf8Value("key", key); statement.Execute(args); + statement.SetResultFieldType(0, ValueType_BinaryString); if (statement.IsDone()) { @@ -4452,6 +4448,8 @@ DatabaseManager& manager, const Orthanc::DatabasePluginMessages::ListKeysValues_Request& request) { + response.mutable_list_keys_values()->Clear(); + LookupFormatter formatter(manager.GetDialect()); std::unique_ptr<DatabaseManager::CachedStatement> statement; @@ -4467,14 +4465,13 @@ else { std::string fromKeyParameter = formatter.GenerateParameter(request.from_key()); - statement.reset(new DatabaseManager::CachedStatement( STATEMENT_FROM_HERE, manager, "SELECT key, value FROM KeyValueStores WHERE storeId= " + storeIdParameter + " AND key > " + fromKeyParameter + " ORDER BY key ASC " + formatter.FormatLimits(0, request.limit()))); } - + statement->Execute(formatter.GetDictionary()); - + if (!statement->IsDone()) { if (statement->GetResultFieldsCount() != 2) @@ -4483,7 +4480,7 @@ } statement->SetResultFieldType(0, ValueType_Utf8String); - statement->SetResultFieldType(1, ValueType_Utf8String); + statement->SetResultFieldType(1, ValueType_BinaryString); while (!statement->IsDone()) { @@ -4507,19 +4504,18 @@ STATEMENT_FROM_HERE, manager, "INSERT INTO Queues VALUES(${AUTOINCREMENT} ${queueId}, ${value})"); + statement.SetParameterType("queueId", ValueType_Utf8String); + statement.SetParameterType("value", ValueType_BinaryString); + Dictionary args; - - statement.SetParameterType("queueId", ValueType_Utf8String); - statement.SetParameterType("value", ValueType_Utf8String); - args.SetUtf8Value("queueId", queueId); - args.SetUtf8Value("value", value); + args.SetBinaryValue("value", value); statement.Execute(args); } - bool IndexBackend::DequeueValue(DatabaseManager& manager, - std::string& value, + bool IndexBackend::DequeueValue(std::string& value, + DatabaseManager& manager, const std::string& queueId, bool fromFront) { @@ -4529,21 +4525,44 @@ std::string queueIdParameter = formatter.GenerateParameter(queueId); - if (fromFront) + switch (manager.GetDialect()) { - 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")); + 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; + + case Dialect_SQLite: + if (fromFront) + { + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "DELETE FROM Queues WHERE id = (SELECT id FROM Queues WHERE queueId=" + queueIdParameter + " ORDER BY id ASC LIMIT 1) RETURNING value")); + } + else + { + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "DELETE FROM Queues WHERE id = (SELECT id FROM Queues WHERE queueId=" + queueIdParameter + " ORDER BY id DESC LIMIT 1) RETURNING value")); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - 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")); - } - + statement->Execute(formatter.GetDictionary()); if (statement->IsDone()) @@ -4552,9 +4571,8 @@ } else { - statement->SetResultFieldType(0, ValueType_Utf8String); + statement->SetResultFieldType(0, ValueType_BinaryString); value = statement->ReadString(0); - return true; } } @@ -4567,12 +4585,13 @@ "SELECT COUNT(*) FROM Queues WHERE queueId = ${queueId}"); statement.SetReadOnly(true); - + statement.SetParameterType("queueId", ValueType_Utf8String); + Dictionary args; - statement.SetParameterType("queueId", ValueType_Utf8String); args.SetUtf8Value("queueId", queueId); statement.Execute(args); + statement.SetResultFieldType(0, ValueType_Integer64); return statement.ReadInteger64(0); } @@ -4589,9 +4608,9 @@ "compressedSize, compressedHash, revision, customData FROM AttachedFiles WHERE uuid = ${uuid}"); statement.SetReadOnly(true); + statement.SetParameterType("uuid", ValueType_Utf8String); Dictionary args; - statement.SetParameterType("uuid", ValueType_Utf8String); args.SetUtf8Value("uuid", request.uuid()); statement.Execute(args); @@ -4630,6 +4649,7 @@ Dictionary args; args.SetUtf8Value("uuid", attachmentUuid); + if (customData.empty()) { args.SetUtf8NullValue("customData");
--- a/Framework/Plugins/IndexBackend.h Fri May 30 15:46:56 2025 +0200 +++ b/Framework/Plugins/IndexBackend.h Fri May 30 21:42:13 2025 +0200 @@ -468,18 +468,18 @@ #if ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES == 1 virtual void StoreKeyValue(DatabaseManager& manager, - const std::string& storeId, - const std::string& key, - const std::string& value) ORTHANC_OVERRIDE; + const std::string& storeId, + const std::string& key, + const std::string& value) ORTHANC_OVERRIDE; virtual void DeleteKeyValue(DatabaseManager& manager, const std::string& storeId, const std::string& key) ORTHANC_OVERRIDE; - virtual bool GetKeyValue(DatabaseManager& manager, - std::string& value, - const std::string& storeId, - const std::string& key) ORTHANC_OVERRIDE; + virtual bool GetKeyValue(std::string& value, + DatabaseManager& manager, + const std::string& storeId, + const std::string& key) ORTHANC_OVERRIDE; virtual void ListKeysValues(Orthanc::DatabasePluginMessages::TransactionResponse& response, DatabaseManager& manager, @@ -491,8 +491,8 @@ const std::string& queueId, const std::string& value) ORTHANC_OVERRIDE; - virtual bool DequeueValue(DatabaseManager& manager, - std::string& value, + virtual bool DequeueValue(std::string& value, + DatabaseManager& manager, const std::string& queueId, bool fromFront) ORTHANC_OVERRIDE;
--- a/Framework/Plugins/IndexUnitTests.h Fri May 30 15:46:56 2025 +0200 +++ b/Framework/Plugins/IndexUnitTests.h Fri May 30 21:42:13 2025 +0200 @@ -217,6 +217,80 @@ } +static void ListKeys(std::set<std::string>& keys, + OrthancDatabases::IndexBackend& db, + OrthancDatabases::DatabaseManager& manager, + const std::string& storeId) +{ + { + Orthanc::DatabasePluginMessages::ListKeysValues_Request request; + request.set_store_id(storeId); + request.set_from_first(true); + request.set_limit(0); + + Orthanc::DatabasePluginMessages::TransactionResponse response; + db.ListKeysValues(response, manager, request); + + keys.clear(); + + for (int i = 0; i < response.list_keys_values().keys_values_size(); i++) + { + const Orthanc::DatabasePluginMessages::ListKeysValues_Response_KeyValue& item = response.list_keys_values().keys_values(i); + keys.insert(item.key()); + + std::string value; + if (!db.GetKeyValue(value, manager, storeId, item.key()) || + value != item.value()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + + { + std::set<std::string> keys2; + + // Alternative implementation using an iterator + Orthanc::DatabasePluginMessages::ListKeysValues_Request request; + request.set_store_id(storeId); + request.set_from_first(true); + request.set_limit(1); + + Orthanc::DatabasePluginMessages::TransactionResponse response; + db.ListKeysValues(response, manager, request); + + while (response.list_keys_values().keys_values_size() > 0) + { + int count = response.list_keys_values().keys_values_size(); + + for (int i = 0; i < count; i++) + { + keys2.insert(response.list_keys_values().keys_values(i).key()); + } + + request.set_from_first(false); + request.set_from_key(response.list_keys_values().keys_values(count - 1).key()); + db.ListKeysValues(response, manager, request); + } + + if (keys.size() != keys2.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + for (std::set<std::string>::const_iterator it = keys.begin(); it != keys.end(); ++it) + { + if (keys2.find(*it) == keys2.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + } +} + + TEST(IndexBackend, Basic) { using namespace OrthancDatabases; @@ -238,7 +312,7 @@ #elif ORTHANC_ENABLE_ODBC == 1 OdbcIndex db(&context, connectionString_, false); #elif ORTHANC_ENABLE_SQLITE == 1 // Must be the last one - SQLiteIndex db(&context); // Open in memory + SQLiteIndex db(&context, "tutu.db"); // Open in memory #else # error Unsupported database backend #endif @@ -803,5 +877,133 @@ } #endif + { + manager->StartTransaction(TransactionType_ReadWrite); + + std::set<std::string> keys; + ListKeys(keys, db, *manager, "test"); + ASSERT_EQ(0u, keys.size()); + + std::string s; + ASSERT_FALSE(db.GetKeyValue(s, *manager, "test", "hello")); + db.DeleteKeyValue(*manager, s, "test"); + + db.StoreKeyValue(*manager, "test", "hello", "world"); + db.StoreKeyValue(*manager, "another", "hello", "world"); + ListKeys(keys, db, *manager, "test"); + ASSERT_EQ(1u, keys.size()); + ASSERT_EQ("hello", *keys.begin()); + ASSERT_TRUE(db.GetKeyValue(s, *manager, "test", "hello")); ASSERT_EQ("world", s); + + db.StoreKeyValue(*manager, "test", "hello", "overwritten"); + ListKeys(keys, db, *manager, "test"); + ASSERT_EQ(1u, keys.size()); + ASSERT_EQ("hello", *keys.begin()); + ASSERT_TRUE(db.GetKeyValue(s, *manager, "test", "hello")); ASSERT_EQ("overwritten", s); + + db.StoreKeyValue(*manager, "test", "hello2", "world2"); + db.StoreKeyValue(*manager, "test", "hello3", "world3"); + + ListKeys(keys, db, *manager, "test"); + ASSERT_EQ(3u, keys.size()); + ASSERT_TRUE(keys.find("hello") != keys.end()); + ASSERT_TRUE(keys.find("hello2") != keys.end()); + ASSERT_TRUE(keys.find("hello3") != keys.end()); + ASSERT_TRUE(db.GetKeyValue(s, *manager, "test", "hello")); ASSERT_EQ("overwritten", s); + ASSERT_TRUE(db.GetKeyValue(s, *manager, "test", "hello2")); ASSERT_EQ("world2", s); + ASSERT_TRUE(db.GetKeyValue(s, *manager, "test", "hello3")); ASSERT_EQ("world3", s); + + db.DeleteKeyValue(*manager, "test", "hello2"); + + ListKeys(keys, db, *manager, "test"); + ASSERT_EQ(2u, keys.size()); + ASSERT_TRUE(keys.find("hello") != keys.end()); + ASSERT_TRUE(keys.find("hello3") != keys.end()); + ASSERT_TRUE(db.GetKeyValue(s, *manager, "test", "hello")); ASSERT_EQ("overwritten", s); + ASSERT_FALSE(db.GetKeyValue(s, *manager, "test", "hello2")); + ASSERT_TRUE(db.GetKeyValue(s, *manager, "test", "hello3")); ASSERT_EQ("world3", s); + + db.DeleteKeyValue(*manager, "test", "nope"); + db.DeleteKeyValue(*manager, "test", "hello"); + db.DeleteKeyValue(*manager, "test", "hello3"); + + ListKeys(keys, db, *manager, "test"); + ASSERT_EQ(0u, keys.size()); + + { + std::string blob; + blob.push_back(0); + blob.push_back(1); + blob.push_back(0); + blob.push_back(2); + db.StoreKeyValue(*manager, "test", "blob", blob); // Storing binary values + } + + ASSERT_TRUE(db.GetKeyValue(s, *manager, "test", "blob")); + ASSERT_EQ(4u, s.size()); + ASSERT_EQ(0u, static_cast<uint8_t>(s[0])); + ASSERT_EQ(1u, static_cast<uint8_t>(s[1])); + ASSERT_EQ(0u, static_cast<uint8_t>(s[2])); + ASSERT_EQ(2u, static_cast<uint8_t>(s[3])); + db.DeleteKeyValue(*manager, "test", "blob"); + ASSERT_FALSE(db.GetKeyValue(s, *manager, "test", "blob")); + + manager->CommitTransaction(); + } + + { + manager->StartTransaction(TransactionType_ReadWrite); + + ASSERT_EQ(0u, db.GetQueueSize(*manager, "test")); + db.EnqueueValue(*manager, "test", "a"); + db.EnqueueValue(*manager, "another", "hello"); + ASSERT_EQ(1u, db.GetQueueSize(*manager, "test")); + db.EnqueueValue(*manager, "test", "b"); + ASSERT_EQ(2u, db.GetQueueSize(*manager, "test")); + db.EnqueueValue(*manager, "test", "c"); + ASSERT_EQ(3u, db.GetQueueSize(*manager, "test")); + + std::string s; + ASSERT_FALSE(db.DequeueValue(s, *manager, "nope", false)); + ASSERT_TRUE(db.DequeueValue(s, *manager, "test", true)); ASSERT_EQ("a", s); + ASSERT_EQ(2u, db.GetQueueSize(*manager, "test")); + ASSERT_TRUE(db.DequeueValue(s, *manager, "test", true)); ASSERT_EQ("b", s); + ASSERT_EQ(1u, db.GetQueueSize(*manager, "test")); + ASSERT_TRUE(db.DequeueValue(s, *manager, "test", true)); ASSERT_EQ("c", s); + ASSERT_EQ(0u, db.GetQueueSize(*manager, "test")); + ASSERT_FALSE(db.DequeueValue(s, *manager, "test", true)); + + db.EnqueueValue(*manager, "test", "a"); + db.EnqueueValue(*manager, "test", "b"); + db.EnqueueValue(*manager, "test", "c"); + + ASSERT_TRUE(db.DequeueValue(s, *manager, "test", false)); ASSERT_EQ("c", s); + ASSERT_TRUE(db.DequeueValue(s, *manager, "test", false)); ASSERT_EQ("b", s); + ASSERT_TRUE(db.DequeueValue(s, *manager, "test", false)); ASSERT_EQ("a", s); + ASSERT_FALSE(db.DequeueValue(s, *manager, "test", false)); + + { + std::string blob; + blob.push_back(0); + blob.push_back(1); + blob.push_back(0); + blob.push_back(2); + db.EnqueueValue(*manager, "test", blob); // Storing binary values + } + + ASSERT_TRUE(db.DequeueValue(s, *manager, "test", true)); + ASSERT_EQ(4u, s.size()); + ASSERT_EQ(0u, static_cast<uint8_t>(s[0])); + ASSERT_EQ(1u, static_cast<uint8_t>(s[1])); + ASSERT_EQ(0u, static_cast<uint8_t>(s[2])); + ASSERT_EQ(2u, static_cast<uint8_t>(s[3])); + + ASSERT_FALSE(db.DequeueValue(s, *manager, "test", true)); + + ASSERT_EQ(1u, db.GetQueueSize(*manager, "another")); + + manager->CommitTransaction(); + } + manager->Close(); }
--- a/Framework/PostgreSQL/PostgreSQLResult.cpp Fri May 30 15:46:56 2025 +0200 +++ b/Framework/PostgreSQL/PostgreSQLResult.cpp Fri May 30 21:42:13 2025 +0200 @@ -168,7 +168,7 @@ CheckColumn(column, 0); Oid oid = PQftype(reinterpret_cast<PGresult*>(result_), column); - if (oid != TEXTOID && oid != VARCHAROID && oid != BYTEAOID) + if (oid != TEXTOID && oid != VARCHAROID) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); } @@ -177,6 +177,22 @@ } + void PostgreSQLResult::GetBinaryString(std::string& target, + unsigned int column) const + { + CheckColumn(column, 0); + + Oid oid = PQftype(reinterpret_cast<PGresult*>(result_), column); + if (oid != BYTEAOID) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); + } + + target.assign(PQgetvalue(reinterpret_cast<PGresult*>(result_), position_, column), + PQgetlength(reinterpret_cast<PGresult*>(result_), position_, column)); + } + + std::string PostgreSQLResult::GetLargeObjectOid(unsigned int column) const { CheckColumn(column, OIDOID); @@ -256,7 +272,14 @@ return new Utf8StringValue(GetString(column)); case BYTEAOID: - return new BinaryStringValue(GetString(column)); + { + std::string s; + GetBinaryString(s, column); + + std::unique_ptr<BinaryStringValue> value(new BinaryStringValue); + value->Swap(s); + return value.release(); + } case OIDOID: return new LargeObjectResult(database_, GetLargeObjectOid(column));
--- a/Framework/PostgreSQL/PostgreSQLResult.h Fri May 30 15:46:56 2025 +0200 +++ b/Framework/PostgreSQL/PostgreSQLResult.h Fri May 30 21:42:13 2025 +0200 @@ -74,6 +74,9 @@ std::string GetString(unsigned int column) const; + void GetBinaryString(std::string& target, + unsigned int column) const; + std::string GetLargeObjectOid(unsigned int column) const; void GetLargeObjectContent(std::string& content,
--- a/Framework/PostgreSQL/PostgreSQLStatement.cpp Fri May 30 15:46:56 2025 +0200 +++ b/Framework/PostgreSQL/PostgreSQLStatement.cpp Fri May 30 21:42:13 2025 +0200 @@ -223,7 +223,7 @@ } oids_[param] = type; - binary_[param] = (type == TEXTOID || type == BYTEAOID || type == OIDOID) ? 0 : 1; + binary_[param] = (type == TEXTOID || type == OIDOID) ? 0 : 1; } @@ -283,16 +283,23 @@ if (PQtransactionStatus(reinterpret_cast<PGconn*>(database_.pg_)) == PQTRANS_INERROR) { + std::string message; + if (result != NULL) { - PQclear(result); + message.assign(PQresultErrorMessage(result)); + PQclear(result); // Frees the memory allocated by "PQresultErrorMessage()" } - + + if (message.empty()) + { + message = "Collision between multiple writers"; + } + #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) - std::string errorString(PQresultErrorMessage(result)); - throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseCannotSerialize, errorString, false); // don't log here, it is handled at higher level + throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseCannotSerialize, message, false); // don't log here, it is handled #else #else - throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Collision between multiple writers"); + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, message); #endif } else if (result == NULL) @@ -454,19 +461,21 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - if (oids_[param] != TEXTOID && oids_[param] != BYTEAOID) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); - } - - if (value.size() == 0) + switch (oids_[param]) { - inputs_->SetItem(param, "", 1 /* end-of-string character */); - } - else - { - inputs_->SetItem(param, value.c_str(), - value.size() + 1); // "+1" for end-of-string character + case TEXTOID: + { + std::string s = value + '\0'; // Make sure that there is an end-of-string character + inputs_->SetItem(param, s.c_str(), s.size()); + break; + } + + case BYTEAOID: + inputs_->SetItem(param, value.c_str(), value.size()); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); } } @@ -547,19 +556,16 @@ break; case ValueType_Utf8String: - BindString(i, dynamic_cast<const Utf8StringValue&> - (value).GetContent()); + BindString(i, dynamic_cast<const Utf8StringValue&>(value).GetContent()); break; case ValueType_BinaryString: - BindString(i, dynamic_cast<const BinaryStringValue&> - (value).GetContent()); + BindString(i, dynamic_cast<const BinaryStringValue&>(value).GetContent()); break; case ValueType_InputFile: { - const InputFileValue& blob = - dynamic_cast<const InputFileValue&>(value); + const InputFileValue& blob = dynamic_cast<const InputFileValue&>(value); PostgreSQLLargeObject largeObject(database_, blob.GetContent()); BindLargeObject(i, largeObject);
--- a/PostgreSQL/Plugins/SQL/PrepareIndex.sql Fri May 30 15:46:56 2025 +0200 +++ b/PostgreSQL/Plugins/SQL/PrepareIndex.sql Fri May 30 21:42:13 2025 +0200 @@ -726,14 +726,14 @@ CREATE TABLE KeyValueStores( storeId TEXT NOT NULL, key TEXT NOT NULL, - value TEXT NOT NULL, + value BYTEA NOT NULL, PRIMARY KEY(storeId, key) -- Prevents duplicates ); CREATE TABLE Queues ( id BIGSERIAL NOT NULL PRIMARY KEY, queueId TEXT NOT NULL, - value TEXT + value BYTEA ); CREATE INDEX QueuesIndex ON Queues (queueId, id);
--- a/SQLite/Plugins/PrepareIndex.sql Fri May 30 15:46:56 2025 +0200 +++ b/SQLite/Plugins/PrepareIndex.sql Fri May 30 21:42:13 2025 +0200 @@ -156,3 +156,20 @@ BEGIN INSERT INTO PatientRecyclingOrder VALUES (NULL, new.internalId); END; + + +-- New in Orthanc 1.12.8 +CREATE TABLE KeyValueStores( + storeId TEXT NOT NULL, + key TEXT NOT NULL, + value BLOB NOT NULL, + PRIMARY KEY(storeId, key) -- Prevents duplicates + ); + +CREATE TABLE Queues ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + queueId TEXT NOT NULL, + value BLOB +); + +CREATE INDEX QueuesIndex ON Queues (queueId, id);