changeset 248:7a4f9bcb0bc2

PostgreSQL: Support of range reads from the storage area
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 14 Apr 2021 09:46:44 +0200
parents e57a5313ffe5
children 7f5ee2b42a86
files Framework/PostgreSQL/PostgreSQLLargeObject.cpp Framework/PostgreSQL/PostgreSQLLargeObject.h Framework/PostgreSQL/PostgreSQLResult.cpp MySQL/NEWS PostgreSQL/NEWS PostgreSQL/Plugins/PostgreSQLStorageArea.h PostgreSQL/UnitTests/PostgreSQLTests.cpp
diffstat 7 files changed, 145 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/PostgreSQL/PostgreSQLLargeObject.cpp	Wed Apr 14 09:18:20 2021 +0200
+++ b/Framework/PostgreSQL/PostgreSQLLargeObject.cpp	Wed Apr 14 09:46:44 2021 +0200
@@ -80,16 +80,6 @@
 
 
   PostgreSQLLargeObject::PostgreSQLLargeObject(PostgreSQLDatabase& database,
-                                               const void* data,
-                                               size_t size) : 
-    database_(database)
-  {
-    Create();
-    Write(data, size);
-  }
-
-
-  PostgreSQLLargeObject::PostgreSQLLargeObject(PostgreSQLDatabase& database,
                                                const std::string& s) : 
     database_(database)
   {
@@ -113,6 +103,24 @@
     int fd_;
     size_t size_;
 
+    void ReadInternal(PGconn* pg,
+                      std::string& target)
+    {
+      for (size_t position = 0; position < target.size(); )
+      {
+        size_t remaining = target.size() - position;
+
+        int nbytes = lo_read(pg, fd_, &target[position], remaining);
+        if (nbytes < 0)
+        {
+          LOG(ERROR) << "PostgreSQL: Unable to read the large object in the database";
+          database_.ThrowException(false);
+        }
+
+        position += static_cast<size_t>(nbytes);
+      }
+    }
+    
   public:
     Reader(PostgreSQLDatabase& database,
            const std::string& oid) : 
@@ -138,9 +146,6 @@
         database.ThrowException(true);
       }
       size_ = static_cast<size_t>(size);
-
-      // Go to the first byte of the object
-      lo_lseek(pg, fd_, 0, SEEK_SET);
     }
 
     ~Reader()
@@ -153,60 +158,67 @@
       return size_;
     }
 
-    void Read(char* target)
+    void ReadWhole(std::string& target)
     {
-      for (size_t position = 0; position < size_; )
+      if (target.size() != size_)
       {
-        size_t remaining = size_ - position;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      
+      PGconn* pg = reinterpret_cast<PGconn*>(database_.pg_);
+
+      // Go to the first byte of the object
+      lo_lseek(pg, fd_, 0, SEEK_SET);
 
-        int nbytes = lo_read(reinterpret_cast<PGconn*>(database_.pg_), fd_, target + position, remaining);
-        if (nbytes < 0)
-        {
-          LOG(ERROR) << "PostgreSQL: Unable to read the large object in the database";
-          database_.ThrowException(false);
-        }
+      ReadInternal(pg, target);
+    }
 
-        position += static_cast<size_t>(nbytes);
-      }
+    void ReadRange(std::string& target,
+                   uint64_t start)
+    {
+      PGconn* pg = reinterpret_cast<PGconn*>(database_.pg_);
+
+      // Go to the first byte of the object
+      lo_lseek(pg, fd_, start, SEEK_SET);
+
+      ReadInternal(pg, target);
     }
   };
   
 
-  void PostgreSQLLargeObject::Read(std::string& target,
-                                   PostgreSQLDatabase& database,
-                                   const std::string& oid)
+  void PostgreSQLLargeObject::ReadWhole(std::string& target,
+                                        PostgreSQLDatabase& database,
+                                        const std::string& oid)
   {
     Reader reader(database, oid);
     target.resize(reader.GetSize());    
 
     if (target.size() > 0)
     {
-      reader.Read(&target[0]);
+      reader.ReadWhole(target);
     }
   }
 
 
-  void PostgreSQLLargeObject::Read(void*& target,
-                                   size_t& size,
-                                   PostgreSQLDatabase& database,
-                                   const std::string& oid)
+  void PostgreSQLLargeObject::ReadRange(std::string& target,
+                                        PostgreSQLDatabase& database,
+                                        const std::string& oid,
+                                        uint64_t start,
+                                        size_t length)
   {
     Reader reader(database, oid);
-    size = reader.GetSize();
 
-    if (size == 0)
-    {
-      target = NULL;
-    }
-    else
+    if (start >= reader.GetSize() ||
+        start + length > reader.GetSize())
     {
-      target = malloc(size);
-      if (target == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
-      }
-      
-      reader.Read(reinterpret_cast<char*>(target));
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRange);
+    }
+    
+    target.resize(length);
+
+    if (target.size() > 0)
+    {
+      reader.ReadRange(target, start);
     }
   }
 
--- a/Framework/PostgreSQL/PostgreSQLLargeObject.h	Wed Apr 14 09:18:20 2021 +0200
+++ b/Framework/PostgreSQL/PostgreSQLLargeObject.h	Wed Apr 14 09:46:44 2021 +0200
@@ -45,23 +45,21 @@
                size_t size);
 
   public:
-    PostgreSQLLargeObject(PostgreSQLDatabase& database,
-                          const void* data,
-                          size_t size);
-
+    // This constructor is used to deal with "InputFileValue"
     PostgreSQLLargeObject(PostgreSQLDatabase& database,
                           const std::string& s);
 
     std::string GetOid() const;
 
-    static void Read(std::string& target,
-                     PostgreSQLDatabase& database,
-                     const std::string& oid);
+    static void ReadWhole(std::string& target,
+                          PostgreSQLDatabase& database,
+                          const std::string& oid);
 
-    static void Read(void*& target,
-                     size_t& size,
-                     PostgreSQLDatabase& database,
-                     const std::string& oid);
+    static void ReadRange(std::string& target,
+                          PostgreSQLDatabase& database,
+                          const std::string& oid,
+                          uint64_t start,
+                          size_t size);
 
     static void Delete(PostgreSQLDatabase& database,
                        const std::string& oid);
--- a/Framework/PostgreSQL/PostgreSQLResult.cpp	Wed Apr 14 09:18:20 2021 +0200
+++ b/Framework/PostgreSQL/PostgreSQLResult.cpp	Wed Apr 14 09:46:44 2021 +0200
@@ -191,7 +191,7 @@
   void PostgreSQLResult::GetLargeObjectContent(std::string& content,
                                                unsigned int column) const
   {
-    PostgreSQLLargeObject::Read(content, database_, GetLargeObjectOid(column));
+    PostgreSQLLargeObject::ReadWhole(content, database_, GetLargeObjectOid(column));
   }
 
 
@@ -211,15 +211,14 @@
 
     virtual void ReadWhole(std::string& target) const ORTHANC_OVERRIDE
     {
-      PostgreSQLLargeObject::Read(target, database_, oid_);
-
+      PostgreSQLLargeObject::ReadWhole(target, database_, oid_);
     }
     
     virtual void ReadRange(std::string& target,
                            uint64_t start,
                            size_t length) const ORTHANC_OVERRIDE
     {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      PostgreSQLLargeObject::ReadRange(target, database_, oid_, start, length);
     }
   };
 
--- a/MySQL/NEWS	Wed Apr 14 09:18:20 2021 +0200
+++ b/MySQL/NEWS	Wed Apr 14 09:46:44 2021 +0200
@@ -1,7 +1,7 @@
 Pending changes in the mainline
 ===============================
 
-* Support of multiple readers/writers, by handling retries in Orthanc SDK 1.9.2
+* Support of multiple readers/writers, by handling retries from Orthanc SDK 1.9.2
 * Support of range reads for the storage area, from Orthanc SDK 1.9.0
 
 
--- a/PostgreSQL/NEWS	Wed Apr 14 09:18:20 2021 +0200
+++ b/PostgreSQL/NEWS	Wed Apr 14 09:46:44 2021 +0200
@@ -1,8 +1,8 @@
 Pending changes in the mainline
 ===============================
 
-* Support of multiple readers/writers, by handling retries in Orthanc SDK 1.9.2
-* Support of "OrthancPluginRegisterStorageArea2()" from Orthanc SDK 1.9.0
+* Support of multiple readers/writers, by handling retries from Orthanc SDK 1.9.2
+* Support of range reads for the storage area, from Orthanc SDK 1.9.0
 
 
 Release 3.3 (2020-12-14)
--- a/PostgreSQL/Plugins/PostgreSQLStorageArea.h	Wed Apr 14 09:18:20 2021 +0200
+++ b/PostgreSQL/Plugins/PostgreSQLStorageArea.h	Wed Apr 14 09:46:44 2021 +0200
@@ -36,7 +36,7 @@
   protected:
     virtual bool HasReadRange() const
     {
-      return false;  // TODO
+      return true;
     }
 
   public:
--- a/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Wed Apr 14 09:18:20 2021 +0200
+++ b/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Wed Apr 14 09:46:44 2021 +0200
@@ -305,7 +305,7 @@
       s.Run();
 
       std::string tmp;
-      PostgreSQLLargeObject::Read(tmp, *pg, obj.GetOid());
+      PostgreSQLLargeObject::ReadWhole(tmp, *pg, obj.GetOid());
       ASSERT_EQ(value, tmp);
 
       t.Commit();
@@ -410,6 +410,77 @@
 }
 
 
+TEST(PostgreSQL, StorageReadRange)
+{
+  std::unique_ptr<OrthancDatabases::PostgreSQLDatabase> database(
+    OrthancDatabases::PostgreSQLDatabase::OpenDatabaseConnection(globalParameters_));
+  
+  OrthancDatabases::PostgreSQLStorageArea storageArea(globalParameters_, true /* clear database */);
+
+  {
+    std::unique_ptr<OrthancDatabases::StorageBackend::IAccessor> accessor(storageArea.CreateAccessor());
+    ASSERT_EQ(0, CountLargeObjects(*database));
+    accessor->Create("uuid", "abcd\0\1\2\3\4\5", 10, OrthancPluginContentType_Unknown);
+    ASSERT_EQ(1u, CountLargeObjects(*database));
+  }
+
+  {
+    std::unique_ptr<OrthancDatabases::StorageBackend::IAccessor> accessor(storageArea.CreateAccessor());
+    ASSERT_EQ(1u, CountLargeObjects(*database));
+
+    std::string s;
+    OrthancDatabases::StorageBackend::ReadWholeToString(s, *accessor, "uuid", OrthancPluginContentType_Unknown);
+    ASSERT_EQ(10u, s.size());
+    ASSERT_EQ('a', s[0]);
+    ASSERT_EQ('d', s[3]);
+    ASSERT_EQ('\0', s[4]);
+    ASSERT_EQ('\5', s[9]);
+
+    OrthancDatabases::StorageBackend::ReadRangeToString(s, *accessor, "uuid", OrthancPluginContentType_Unknown, 0, 0);
+    ASSERT_TRUE(s.empty());
+
+    OrthancDatabases::StorageBackend::ReadRangeToString(s, *accessor, "uuid", OrthancPluginContentType_Unknown, 0, 1);
+    ASSERT_EQ(1u, s.size());
+    ASSERT_EQ('a', s[0]);
+
+    OrthancDatabases::StorageBackend::ReadRangeToString(s, *accessor, "uuid", OrthancPluginContentType_Unknown, 4, 1);
+    ASSERT_EQ(1u, s.size());
+    ASSERT_EQ('\0', s[0]);
+
+    OrthancDatabases::StorageBackend::ReadRangeToString(s, *accessor, "uuid", OrthancPluginContentType_Unknown, 9, 1);
+    ASSERT_EQ(1u, s.size());
+    ASSERT_EQ('\5', s[0]);
+
+    // Cannot read non-empty range after the end of the string. NB:
+    // The behavior on range (10, 0) is different than in MySQL!
+    ASSERT_THROW(OrthancDatabases::StorageBackend::ReadRangeToString(
+                   s, *accessor, "uuid", OrthancPluginContentType_Unknown, 10, 0), Orthanc::OrthancException);
+
+    ASSERT_THROW(OrthancDatabases::StorageBackend::ReadRangeToString(
+                   s, *accessor, "uuid", OrthancPluginContentType_Unknown, 10, 1), Orthanc::OrthancException);
+
+    OrthancDatabases::StorageBackend::ReadRangeToString(s, *accessor, "uuid", OrthancPluginContentType_Unknown, 0, 4);
+    ASSERT_EQ(4u, s.size());
+    ASSERT_EQ('a', s[0]);
+    ASSERT_EQ('b', s[1]);
+    ASSERT_EQ('c', s[2]);
+    ASSERT_EQ('d', s[3]);
+
+    OrthancDatabases::StorageBackend::ReadRangeToString(s, *accessor, "uuid", OrthancPluginContentType_Unknown, 4, 6);
+    ASSERT_EQ(6u, s.size());
+    ASSERT_EQ('\0', s[0]);
+    ASSERT_EQ('\1', s[1]);
+    ASSERT_EQ('\2', s[2]);
+    ASSERT_EQ('\3', s[3]);
+    ASSERT_EQ('\4', s[4]);
+    ASSERT_EQ('\5', s[5]);
+
+    ASSERT_THROW(OrthancDatabases::StorageBackend::ReadRangeToString(
+                   s, *accessor, "uuid", OrthancPluginContentType_Unknown, 4, 7), Orthanc::OrthancException);
+  }
+}
+
+
 TEST(PostgreSQL, ImplicitTransaction)
 {
   std::unique_ptr<PostgreSQLDatabase> db(CreateTestDatabase());