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();
+}