# HG changeset patch # User Sebastien Jodogne # Date 1618386404 -7200 # Node ID 7a4f9bcb0bc2caad109b4caebce4fb279238004e # Parent e57a5313ffe525ec822d5241c96eac70b1766442 PostgreSQL: Support of range reads from the storage area diff -r e57a5313ffe5 -r 7a4f9bcb0bc2 Framework/PostgreSQL/PostgreSQLLargeObject.cpp --- 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(nbytes); + } + } + public: Reader(PostgreSQLDatabase& database, const std::string& oid) : @@ -138,9 +146,6 @@ database.ThrowException(true); } size_ = static_cast(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(database_.pg_); + + // Go to the first byte of the object + lo_lseek(pg, fd_, 0, SEEK_SET); - int nbytes = lo_read(reinterpret_cast(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(nbytes); - } + void ReadRange(std::string& target, + uint64_t start) + { + PGconn* pg = reinterpret_cast(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(target)); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRange); + } + + target.resize(length); + + if (target.size() > 0) + { + reader.ReadRange(target, start); } } diff -r e57a5313ffe5 -r 7a4f9bcb0bc2 Framework/PostgreSQL/PostgreSQLLargeObject.h --- 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); diff -r e57a5313ffe5 -r 7a4f9bcb0bc2 Framework/PostgreSQL/PostgreSQLResult.cpp --- 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); } }; diff -r e57a5313ffe5 -r 7a4f9bcb0bc2 MySQL/NEWS --- 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 diff -r e57a5313ffe5 -r 7a4f9bcb0bc2 PostgreSQL/NEWS --- 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) diff -r e57a5313ffe5 -r 7a4f9bcb0bc2 PostgreSQL/Plugins/PostgreSQLStorageArea.h --- 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: diff -r e57a5313ffe5 -r 7a4f9bcb0bc2 PostgreSQL/UnitTests/PostgreSQLTests.cpp --- 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 database( + OrthancDatabases::PostgreSQLDatabase::OpenDatabaseConnection(globalParameters_)); + + OrthancDatabases::PostgreSQLStorageArea storageArea(globalParameters_, true /* clear database */); + + { + std::unique_ptr 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 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 db(CreateTestDatabase());