Mercurial > hg > orthanc
changeset 6119:7213cfbd2784 attach-custom-data
replaced StatelessDatabaseOperations::ListKeys() by KeysValuesIterator
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 20 May 2025 16:20:42 +0200 |
parents | fec888c37d4e |
children | 756983d498bb |
files | OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Sources/Database/IDatabaseWrapper.h OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.h OrthancServer/UnitTestsSources/ServerIndexTests.cpp |
diffstat | 9 files changed, 421 insertions(+), 62 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Tue May 20 14:33:17 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Tue May 20 16:20:42 2025 +0200 @@ -1471,10 +1471,12 @@ throw OrthancException(ErrorCode_InternalError); // Not supported } - virtual void ListKeys(std::list<std::string>& keys, - const std::string& storeId, - uint64_t since, - uint64_t limit) ORTHANC_OVERRIDE + virtual void ListKeysValues(std::list<std::string>& keys, + std::list<std::string>& values, + const std::string& storeId, + bool first, + const std::string& from, + uint64_t limit) ORTHANC_OVERRIDE { throw OrthancException(ErrorCode_InternalError); // Not supported }
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp Tue May 20 14:33:17 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp Tue May 20 16:20:42 2025 +0200 @@ -1083,10 +1083,12 @@ throw OrthancException(ErrorCode_InternalError); // Not supported } - virtual void ListKeys(std::list<std::string>& keys, - const std::string& storeId, - uint64_t since, - uint64_t limit) ORTHANC_OVERRIDE + virtual void ListKeysValues(std::list<std::string>& keys, + std::list<std::string>& values, + const std::string& storeId, + bool first, + const std::string& from, + uint64_t limit) ORTHANC_OVERRIDE { throw OrthancException(ErrorCode_InternalError); // Not supported }
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Tue May 20 14:33:17 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Tue May 20 16:20:42 2025 +0200 @@ -1842,10 +1842,12 @@ throw OrthancException(ErrorCode_NotImplemented); // TODO_ATTACH_CUSTOM_DATA } - virtual void ListKeys(std::list<std::string>& keys, - const std::string& storeId, - uint64_t since, - uint64_t limit) ORTHANC_OVERRIDE + virtual void ListKeysValues(std::list<std::string>& keys, + std::list<std::string>& values, + const std::string& storeId, + bool first, + const std::string& from, + uint64_t limit) ORTHANC_OVERRIDE { throw OrthancException(ErrorCode_InternalError); // TODO_ATTACH_CUSTOM_DATA }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue May 20 14:33:17 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue May 20 16:20:42 2025 +0200 @@ -4762,7 +4762,7 @@ std::list<std::string> keys; - lock.GetContext().GetIndex().ListKeys(keys, parameters.storeId, parameters.since, parameters.limit); + //lock.GetContext().GetIndex().ListKeys(keys, parameters.storeId, parameters.since, parameters.limit); CopyStringList(*(parameters.keys), keys); }
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h Tue May 20 14:33:17 2025 +0200 +++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h Tue May 20 16:20:42 2025 +0200 @@ -449,10 +449,12 @@ const std::string& key) = 0; // New in Orthanc 1.12.99 - virtual void ListKeys(std::list<std::string>& keys, - const std::string& storeId, - uint64_t since, - uint64_t limit) = 0; + virtual void ListKeysValues(std::list<std::string>& keys /* out */, + std::list<std::string>& values /* out */, + const std::string& storeId, + bool first, + const std::string& from /* only used if "first == false" */, + uint64_t limit /* maximum number of elements */) = 0; // New in Orthanc 1.12.99 virtual void EnqueueValue(const std::string& queueId,
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Tue May 20 14:33:17 2025 +0200 +++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Tue May 20 16:20:42 2025 +0200 @@ -2182,20 +2182,39 @@ } // New in Orthanc 1.12.99 - virtual void ListKeys(std::list<std::string>& keys, - const std::string& storeId, - uint64_t since, - uint64_t limit) ORTHANC_OVERRIDE + virtual void ListKeysValues(std::list<std::string>& keys /* out */, + std::list<std::string>& values /* out */, + const std::string& storeId, + bool first, + const std::string& from /* only used if "first == false" */, + uint64_t limit) ORTHANC_OVERRIDE { - LookupFormatter formatter; - - std::string sql = "SELECT key FROM KeyValueStores WHERE storeId=? ORDER BY key ASC " + formatter.FormatLimits(since, limit); - SQLite::Statement s(db_, SQLITE_FROM_HERE_DYNAMIC(sql), sql); - s.BindString(0, storeId); - - while (s.Step()) + int64_t actualLimit = limit; + if (limit == 0) + { + actualLimit = -1; // In SQLite, "if negative, there is no upper bound on the number of rows returned" + } + + std::unique_ptr<SQLite::Statement> statement; + + if (first) { - keys.push_back(s.ColumnString(0)); + statement.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT key, value FROM KeyValueStores WHERE storeId=? ORDER BY key ASC LIMIT ?")); + statement->BindString(0, storeId); + statement->BindInt64(1, actualLimit); + } + else + { + statement.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT key, value FROM KeyValueStores WHERE storeId=? AND key>? ORDER BY key ASC LIMIT ?")); + statement->BindString(0, storeId); + statement->BindString(1, from); + statement->BindInt64(2, actualLimit); + } + + while (statement->Step()) + { + keys.push_back(statement->ColumnString(0)); + values.push_back(statement->ColumnString(1)); } } @@ -2244,7 +2263,7 @@ rowId = s->ColumnInt64(0); value = s->ColumnString(1); - SQLite::Statement s2(db_, SQLITE_FROM_HERE, + SQLite::Statement s2(db_, SQLITE_FROM_HERE, "DELETE FROM Queues WHERE id = ?"); s2.BindInt64(0, rowId); s2.Run();
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Tue May 20 14:33:17 2025 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Tue May 20 16:20:42 2025 +0200 @@ -3421,28 +3421,6 @@ return operations.HasFound(); } - void StatelessDatabaseOperations::ListKeys(std::list<std::string>& keys, - const std::string& storeId, - uint64_t since, - uint64_t limit) - { - class Operations : public ReadOnlyOperationsT4<std::list<std::string>&, const std::string&, uint64_t, uint64_t> - { - public: - Operations() - {} - - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - transaction.ListKeys(tuple.get<0>(), tuple.get<1>(), tuple.get<2>(), tuple.get<3>()); - } - }; - - Operations operations; - operations.Apply(*this, keys, storeId, since, limit); - } - void StatelessDatabaseOperations::EnqueueValue(const std::string& queueId, const std::string& value) { @@ -3588,4 +3566,125 @@ Apply(operations); } + StatelessDatabaseOperations::KeysValuesIterator::KeysValuesIterator(StatelessDatabaseOperations& db, + const std::string& storeId) : + db_(db), + state_(State_Waiting), + storeId_(storeId), + limit_(100) + { + } + + bool StatelessDatabaseOperations::KeysValuesIterator::Next() + { + if (state_ == State_Done) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (state_ == State_Available) + { + assert(currentKey_ != keys_.end()); + assert(currentValue_ != values_.end()); + ++currentKey_; + ++currentValue_; + + if (currentKey_ != keys_.end() && + currentValue_ != values_.end()) + { + // A value is still available in the last keys-values block fetched from the database + return true; + } + else if (currentKey_ != keys_.end() || + currentValue_ != values_.end()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + class Operations : public ReadOnlyOperationsT6<std::list<std::string>&, std::list<std::string>&, const std::string&, bool, const std::string&, uint64_t> + { + public: + virtual void ApplyTuple(ReadOnlyTransaction& transaction, + const Tuple& tuple) ORTHANC_OVERRIDE + { + transaction.ListKeysValues(tuple.get<0>(), tuple.get<1>(), tuple.get<2>(), tuple.get<3>(), tuple.get<4>(), tuple.get<5>()); + } + }; + + if (state_ == State_Waiting) + { + keys_.clear(); + values_.clear(); + + Operations operations; + operations.Apply(db_, keys_, values_, storeId_, true, "", limit_); + } + else + { + assert(state_ == State_Available); + if (keys_.empty()) + { + state_ = State_Done; + return false; + } + else + { + const std::string lastKey = keys_.back(); + keys_.clear(); + values_.clear(); + + Operations operations; + operations.Apply(db_, keys_, values_, storeId_, false, lastKey, limit_); + } + } + + if (keys_.size() != values_.size()) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + if (keys_.empty() && + values_.empty()) + { + state_ = State_Done; + return false; + } + else if (!keys_.empty() && + !values_.empty()) + { + state_ = State_Available; + currentKey_ = keys_.begin(); + currentValue_ = values_.begin(); + return true; + } + else + { + throw OrthancException(ErrorCode_InternalError); // Should never happen + } + } + + const std::string &StatelessDatabaseOperations::KeysValuesIterator::GetKey() const + { + if (state_ == State_Available) + { + return *currentKey_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + const std::string &StatelessDatabaseOperations::KeysValuesIterator::GetValue() const + { + if (state_ == State_Available) + { + return *currentValue_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } }
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Tue May 20 14:33:17 2025 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Tue May 20 16:20:42 2025 +0200 @@ -313,12 +313,14 @@ return transaction_.GetQueueSize(queueId); } - void ListKeys(std::list<std::string>& keys, - const std::string& storeId, - uint64_t since, - uint64_t limit) + void ListKeysValues(std::list<std::string>& keys, + std::list<std::string>& values, + const std::string& storeId, + bool first, + const std::string& from, + uint64_t limit) { - return transaction_.ListKeys(keys, storeId, since, limit); + return transaction_.ListKeysValues(keys, values, storeId, first, from, limit); } }; @@ -807,11 +809,6 @@ const std::string& storeId, const std::string& key); - void ListKeys(std::list<std::string>& keys, - const std::string& storeId, - uint64_t since, - uint64_t limit); - void EnqueueValue(const std::string& queueId, const std::string& value); @@ -820,5 +817,45 @@ QueueOrigin origin); uint64_t GetQueueSize(const std::string& queueId); + + class KeysValuesIterator : public boost::noncopyable + { + private: + enum State + { + State_Waiting, + State_Available, + State_Done + }; + + StatelessDatabaseOperations& db_; + State state_; + std::string storeId_; + uint64_t limit_; + std::list<std::string> keys_; + std::list<std::string> values_; + std::list<std::string>::const_iterator currentKey_; + std::list<std::string>::const_iterator currentValue_; + + public: + KeysValuesIterator(StatelessDatabaseOperations& db, + const std::string& storeId); + + void SetLimit(uint64_t limit) + { + limit_ = limit; + } + + uint64_t GetLimit() const + { + return limit_; + } + + bool Next(); + + const std::string& GetKey() const; + + const std::string& GetValue() const; + }; }; }
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp Tue May 20 14:33:17 2025 +0200 +++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp Tue May 20 16:20:42 2025 +0200 @@ -197,6 +197,74 @@ transaction_->ApplyLookupResources(result, NULL, lookup, level, noLabel, LabelsConstraint_All, 0 /* no limit */); } }; + + class DummyTransactionContextFactory : public StatelessDatabaseOperations::ITransactionContextFactory + { + public: + virtual StatelessDatabaseOperations::ITransactionContext* Create() + { + class DummyTransactionContext : public StatelessDatabaseOperations::ITransactionContext + { + public: + virtual void SignalRemainingAncestor(ResourceType parentType, + const std::string& publicId) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } + + virtual void SignalAttachmentDeleted(const FileInfo& info) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } + + virtual void SignalResourceDeleted(ResourceType type, + const std::string& publicId) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } + + virtual void Commit() ORTHANC_OVERRIDE + { + } + + virtual int64_t GetCompressedSizeDelta() ORTHANC_OVERRIDE + { + return 0; + } + + virtual bool IsUnstableResource(Orthanc::ResourceType type, + int64_t id) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } + + virtual bool LookupRemainingLevel(std::string& remainingPublicId /* out */, + ResourceType& remainingLevel /* out */) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } + + virtual void MarkAsUnstable(Orthanc::ResourceType type, + int64_t id, + const std::string& publicId) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } + + virtual void SignalAttachmentsAdded(uint64_t compressedSize) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } + + virtual void SignalChange(const ServerIndexChange& change) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } + }; + + return new DummyTransactionContext; + } + }; } @@ -1058,3 +1126,131 @@ ASSERT_FALSE(ServerToolbox::IsValidLabel("&")); ASSERT_FALSE(ServerToolbox::IsValidLabel(".")); } + + +TEST(SQLiteDatabaseWrapper, KeyValueStores) +{ + SQLiteDatabaseWrapper db; // The SQLite DB is in memory + db.Open(); + + { + StatelessDatabaseOperations op(db, false); + op.SetTransactionContextFactory(new DummyTransactionContextFactory); + + for (unsigned int limit = 0; limit < 5; limit++) + { + StatelessDatabaseOperations::KeysValuesIterator it(op, "test"); + it.SetLimit(limit); + ASSERT_THROW(it.GetValue(), OrthancException); + ASSERT_THROW(it.GetKey(), OrthancException); + ASSERT_FALSE(it.Next()); + ASSERT_THROW(it.GetValue(), OrthancException); + ASSERT_THROW(it.GetKey(), OrthancException); + } + + op.StoreKeyValue("test", "hello", "world"); + + for (unsigned int limit = 0; limit < 5; limit++) + { + StatelessDatabaseOperations::KeysValuesIterator it(op, "test"); + it.SetLimit(limit); + ASSERT_THROW(it.GetValue(), OrthancException); + ASSERT_THROW(it.GetKey(), OrthancException); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello", it.GetKey()); + ASSERT_EQ("world", it.GetValue()); + ASSERT_FALSE(it.Next()); + ASSERT_THROW(it.GetValue(), OrthancException); + ASSERT_THROW(it.GetKey(), OrthancException); + } + + op.StoreKeyValue("test", "hello2", "world2"); + op.StoreKeyValue("test", "hello3", "world3"); + + for (unsigned int limit = 0; limit < 5; limit++) + { + StatelessDatabaseOperations::KeysValuesIterator it(op, "test"); + it.SetLimit(limit); + ASSERT_THROW(it.GetValue(), OrthancException); + ASSERT_THROW(it.GetKey(), OrthancException); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello", it.GetKey()); + ASSERT_EQ("world", it.GetValue()); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello2", it.GetKey()); + ASSERT_EQ("world2", it.GetValue()); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello3", it.GetKey()); + ASSERT_EQ("world3", it.GetValue()); + ASSERT_FALSE(it.Next()); + ASSERT_THROW(it.GetValue(), OrthancException); + ASSERT_THROW(it.GetKey(), OrthancException); + } + + op.DeleteKeyValue("test", "hello2"); + + for (unsigned int limit = 0; limit < 5; limit++) + { + StatelessDatabaseOperations::KeysValuesIterator it(op, "test"); + it.SetLimit(limit); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello", it.GetKey()); + ASSERT_EQ("world", it.GetValue()); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello3", it.GetKey()); + ASSERT_EQ("world3", it.GetValue()); + ASSERT_FALSE(it.Next()); + } + + std::string s; + ASSERT_TRUE(op.GetKeyValue(s, "test", "hello")); ASSERT_EQ("world", s); + ASSERT_TRUE(op.GetKeyValue(s, "test", "hello3")); ASSERT_EQ("world3", s); + ASSERT_FALSE(op.GetKeyValue(s, "test", "hello2")); + + op.DeleteKeyValue("test", "nope"); + + op.DeleteKeyValue("test", "hello"); + op.DeleteKeyValue("test", "hello3"); + + for (unsigned int limit = 0; limit < 5; limit++) + { + StatelessDatabaseOperations::KeysValuesIterator it(op, "test"); + it.SetLimit(limit); + ASSERT_FALSE(it.Next()); + } + } + + db.Close(); +} + + +TEST(SQLiteDatabaseWrapper, Queues) +{ + SQLiteDatabaseWrapper db; // The SQLite DB is in memory + db.Open(); + + { + StatelessDatabaseOperations op(db, false); + op.SetTransactionContextFactory(new DummyTransactionContextFactory); + + ASSERT_EQ(0u, op.GetQueueSize("test")); + op.EnqueueValue("test", "hello"); + ASSERT_EQ(1u, op.GetQueueSize("test")); + op.EnqueueValue("test", "world"); + ASSERT_EQ(2u, op.GetQueueSize("test")); + + std::string s; + ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Back)); ASSERT_EQ("world", s); + ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Back)); ASSERT_EQ("hello", s); + ASSERT_FALSE(op.DequeueValue(s, "test", QueueOrigin_Back)); + + op.EnqueueValue("test", "hello"); + op.EnqueueValue("test", "world"); + + ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Front)); ASSERT_EQ("hello", s); + ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Front)); ASSERT_EQ("world", s); + ASSERT_FALSE(op.DequeueValue(s, "test", QueueOrigin_Front)); + } + + db.Close(); +}