From 6bb6dd6add7b2e55189df821ef4aa7f2a3da00fd Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 8 Nov 2023 13:53:27 +0100 Subject: [PATCH] Added some foreachSubRange --- .../include/services/database/Types.hpp | 365 +++++++++--------- src/libs/services/database/test/Common.cpp | 278 +++++++------ 2 files changed, 349 insertions(+), 294 deletions(-) diff --git a/src/libs/services/database/include/services/database/Types.hpp b/src/libs/services/database/include/services/database/Types.hpp index a07ec274..881d3f97 100644 --- a/src/libs/services/database/include/services/database/Types.hpp +++ b/src/libs/services/database/include/services/database/Types.hpp @@ -26,208 +26,225 @@ namespace Database { - // Caution: do not change enum values if they are set! + // Caution: do not change enum values if they are set! - // Request: - // size = 0 => no size limit! - // Response (via RangeResults) - // size => results size - struct Range - { - std::size_t offset {}; - std::size_t size {}; + // Request: + // size = 0 => no size limit! + // Response (via RangeResults) + // size => results size + struct Range + { + std::size_t offset{}; + std::size_t size{}; - // TODO remove this - operator bool() const { return size != 0; } - }; + // TODO remove this + operator bool() const { return size != 0; } + }; - template - struct RangeResults - { - Range range; - std::vector results; - bool moreResults{}; + // Func must return true to continue iterating + template + void foreachSubRange(Range range, std::size_t subRangeSize, Func&& func) + { + assert(subRangeSize > 0); - RangeResults getSubRange(Range subRange) - { - assert(subRange.offset >= range.offset); + Range subRange{ range.offset, std::min(range.size, subRangeSize) }; + while (subRange.size > 0) + { + if (!func(subRange)) + break; - if (!subRange.size) - subRange.size = range.size - (subRange.offset - range.offset); + subRange.offset += subRange.size; + subRange.size = std::min(subRangeSize, range.size - (subRange.offset - range.offset)); + } + } - subRange.offset = std::min(subRange.offset, range.offset + range.size); - subRange.size = std::min(subRange.size, range.offset + range.size - subRange.offset); + template + struct RangeResults + { + Range range; + std::vector results; + bool moreResults{}; - RangeResults subResults; + RangeResults getSubRange(Range subRange) + { + assert(subRange.offset >= range.offset); - auto itBegin {std::cbegin(results) + subRange.offset - range.offset}; - auto itEnd {itBegin + subRange.size}; - subResults.results.reserve(std::distance(itBegin, itEnd)); - std::copy(itBegin, itEnd, std::back_inserter(subResults.results)); + if (!subRange.size) + subRange.size = range.size - (subRange.offset - range.offset); - subResults.range = subRange; - if (subRange.offset + subRange.size == range.offset + range.size) - subResults.moreResults = moreResults; - else - subResults.moreResults = true; + subRange.offset = std::min(subRange.offset, range.offset + range.size); + subRange.size = std::min(subRange.size, range.offset + range.size - subRange.offset); - return subResults; - } - }; + RangeResults subResults; - struct DateRange - { - Wt::WDate begin; - Wt::WDate end; + auto itBegin{ std::cbegin(results) + subRange.offset - range.offset }; + auto itEnd{ itBegin + subRange.size }; + subResults.results.reserve(std::distance(itBegin, itEnd)); + std::copy(itBegin, itEnd, std::back_inserter(subResults.results)); - static DateRange fromYearRange(int from, int to); - }; + subResults.range = subRange; + if (subRange.offset + subRange.size == range.offset + range.size) + subResults.moreResults = moreResults; + else + subResults.moreResults = true; - struct DiscInfo - { - std::size_t position; - std::string name; - }; + return subResults; + } + }; - enum class ArtistSortMethod - { - None, - ByName, - BySortName, - Random, - LastWritten, - StarredDateDesc, - }; + struct DateRange + { + Wt::WDate begin; + Wt::WDate end; - enum class ReleaseSortMethod - { - None, - Name, - Date, - OriginalDate, - OriginalDateDesc, - Random, - LastWritten, - StarredDateDesc, - }; + static DateRange fromYearRange(int from, int to); + }; - enum class TrackListSortMethod - { - None, - Name, - LastModifiedDesc, - }; + struct DiscInfo + { + std::size_t position; + std::string name; + }; - enum class TrackSortMethod - { - None, - Random, - LastWritten, - StarredDateDesc, - Name, - DateDescAndRelease, - Release, // order by disc/track number - TrackList, // order by asc order in tracklist - }; + enum class ArtistSortMethod + { + None, + ByName, + BySortName, + Random, + LastWritten, + StarredDateDesc, + }; - enum class TrackArtistLinkType - { - Artist = 0, // regular track artist - Arranger = 1, - Composer = 2, - Conductor = 3, - Lyricist = 4, - Mixer = 5, - Performer = 6, - Producer = 7, - ReleaseArtist = 8, - Remixer = 9, - Writer = 10, - }; + enum class ReleaseSortMethod + { + None, + Name, + Date, + OriginalDate, + OriginalDateDesc, + Random, + LastWritten, + StarredDateDesc, + }; - // User selectable audio file formats - enum class AudioFormat - { - MP3 = 1, - OGG_OPUS = 2, - OGG_VORBIS = 3, - WEBM_VORBIS = 4, - MATROSKA_OPUS = 5, - }; + enum class TrackListSortMethod + { + None, + Name, + LastModifiedDesc, + }; - using Bitrate = std::uint32_t; - // Do not remove values! - void visitAllowedAudioBitrates(std::function); - bool isAudioBitrateAllowed(Bitrate bitrate); + enum class TrackSortMethod + { + None, + Random, + LastWritten, + StarredDateDesc, + Name, + DateDescAndRelease, + Release, // order by disc/track number + TrackList, // order by asc order in tracklist + }; - enum class ScrobblingBackend - { - Internal = 0, - ListenBrainz = 1, - }; + enum class TrackArtistLinkType + { + Artist = 0, // regular track artist + Arranger = 1, + Composer = 2, + Conductor = 3, + Lyricist = 4, + Mixer = 5, + Performer = 6, + Producer = 7, + ReleaseArtist = 8, + Remixer = 9, + Writer = 10, + }; - enum class FeedbackBackend - { - Internal = 0, - ListenBrainz = 1, - }; + // User selectable audio file formats + enum class AudioFormat + { + MP3 = 1, + OGG_OPUS = 2, + OGG_VORBIS = 3, + WEBM_VORBIS = 4, + MATROSKA_OPUS = 5, + }; - enum class SyncState - { - PendingAdd = 0, - Synchronized = 1, - PendingRemove = 2, - }; + using Bitrate = std::uint32_t; + // Do not remove values! + void visitAllowedAudioBitrates(std::function); + bool isAudioBitrateAllowed(Bitrate bitrate); - enum class UserType - { - REGULAR = 0, - ADMIN = 1, - DEMO = 2, - }; + enum class ScrobblingBackend + { + Internal = 0, + ListenBrainz = 1, + }; - enum class UITheme - { - Light = 0, - Dark = 1, - }; + enum class FeedbackBackend + { + Internal = 0, + ListenBrainz = 1, + }; - enum class SubsonicArtistListMode - { - AllArtists = 0, - ReleaseArtists = 1, - TrackArtists = 2, - }; + enum class SyncState + { + PendingAdd = 0, + Synchronized = 1, + PendingRemove = 2, + }; - enum class TrackListType - { - Playlist, // user controlled playlists - Internal, // internal usage (current playqueue, history, ...) - }; + enum class UserType + { + REGULAR = 0, + ADMIN = 1, + DEMO = 2, + }; - // as defined in https://musicbrainz.org/doc/Release_Group/Type - enum class ReleaseTypePrimary - { - Album, - Single, - EP, - Broadcast, - Other, - }; + enum class UITheme + { + Light = 0, + Dark = 1, + }; - enum class ReleaseTypeSecondary - { - Compilation, - Soundtrack, - Spokenword, - Interview, - Audiobook, - AudioDrama, - Live, - Remix, - DJMix, - Mixtape_Street, - Demo, - }; + enum class SubsonicArtistListMode + { + AllArtists = 0, + ReleaseArtists = 1, + TrackArtists = 2, + }; + + enum class TrackListType + { + Playlist, // user controlled playlists + Internal, // internal usage (current playqueue, history, ...) + }; + + // as defined in https://musicbrainz.org/doc/Release_Group/Type + enum class ReleaseTypePrimary + { + Album, + Single, + EP, + Broadcast, + Other, + }; + + enum class ReleaseTypeSecondary + { + Compilation, + Soundtrack, + Spokenword, + Interview, + Audiobook, + AudioDrama, + Live, + Remix, + DJMix, + Mixtape_Street, + Demo, + }; } diff --git a/src/libs/services/database/test/Common.cpp b/src/libs/services/database/test/Common.cpp index c32d8932..68c49382 100644 --- a/src/libs/services/database/test/Common.cpp +++ b/src/libs/services/database/test/Common.cpp @@ -36,174 +36,212 @@ #include "services/database/User.hpp" TmpDatabase::TmpDatabase() -: _tmpFile {std::tmpnam(nullptr)} -, _fileDeleter {_tmpFile} -, _db {_tmpFile} + : _tmpFile{ std::tmpnam(nullptr) } + , _fileDeleter{ _tmpFile } + , _db{ _tmpFile } { } Database::Db& TmpDatabase::getDb() { - return _db; + return _db; } DatabaseFixture::~DatabaseFixture() { - testDatabaseEmpty(); + testDatabaseEmpty(); } void DatabaseFixture::SetUpTestCase() { - _tmpDb = std::make_unique(); - { - Database::Session s {_tmpDb->getDb()}; - s.prepareTables(); - s.analyze(); + _tmpDb = std::make_unique(); + { + Database::Session s{ _tmpDb->getDb() }; + s.prepareTables(); + s.analyze(); - // remove default created entries - { - auto transaction {s.createUniqueTransaction()}; - auto clusterTypes {Database::ClusterType::find(s, Database::Range {})}; - for (auto clusterTypeId : clusterTypes.results) - { - auto clusterType {Database::ClusterType::find(s, clusterTypeId)}; - clusterType.remove(); - } - } - } + // remove default created entries + { + auto transaction{ s.createUniqueTransaction() }; + auto clusterTypes{ Database::ClusterType::find(s, Database::Range {}) }; + for (auto clusterTypeId : clusterTypes.results) + { + auto clusterType{ Database::ClusterType::find(s, clusterTypeId) }; + clusterType.remove(); + } + } + } } void DatabaseFixture::TearDownTestCase() { - _tmpDb.reset(); + _tmpDb.reset(); } void DatabaseFixture::testDatabaseEmpty() { - using namespace Database; + using namespace Database; - auto uniqueTransaction {session.createUniqueTransaction()}; + auto uniqueTransaction{ session.createUniqueTransaction() }; - EXPECT_EQ(Artist::getCount(session), 0); - EXPECT_EQ(Cluster::getCount(session), 0); - EXPECT_EQ(ClusterType::getCount(session), 0); - EXPECT_EQ(Listen::getCount(session), 0); - EXPECT_EQ(Release::getCount(session), 0); - EXPECT_EQ(StarredArtist::getCount(session), 0); - EXPECT_EQ(StarredRelease::getCount(session), 0); - EXPECT_EQ(StarredTrack::getCount(session), 0); - EXPECT_EQ(Track::getCount(session), 0); - EXPECT_EQ(TrackBookmark::getCount(session), 0); - EXPECT_EQ(TrackList::getCount(session), 0); - EXPECT_EQ(User::getCount(session), 0); + EXPECT_EQ(Artist::getCount(session), 0); + EXPECT_EQ(Cluster::getCount(session), 0); + EXPECT_EQ(ClusterType::getCount(session), 0); + EXPECT_EQ(Listen::getCount(session), 0); + EXPECT_EQ(Release::getCount(session), 0); + EXPECT_EQ(StarredArtist::getCount(session), 0); + EXPECT_EQ(StarredRelease::getCount(session), 0); + EXPECT_EQ(StarredTrack::getCount(session), 0); + EXPECT_EQ(Track::getCount(session), 0); + EXPECT_EQ(TrackBookmark::getCount(session), 0); + EXPECT_EQ(TrackList::getCount(session), 0); + EXPECT_EQ(User::getCount(session), 0); } TEST_F(DatabaseFixture, Common_subRangeEmpty) { - using namespace Database; + using namespace Database; - RangeResults results; - results.range = Range {0, 0}; - results.results = {}; - results.moreResults = false; + RangeResults results; + results.range = Range{ 0, 0 }; + results.results = {}; + results.moreResults = false; - { - auto subRange {results.getSubRange(Range {0, 0})}; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 0); - EXPECT_EQ(subRange.range, Range {}); - } - { - auto subRange {results.getSubRange(Range {0, 1})}; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 0); - } + { + auto subRange{ results.getSubRange(Range {0, 0}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 0); + EXPECT_EQ(subRange.range, Range{}); + } + { + auto subRange{ results.getSubRange(Range {0, 1}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 0); + } +} + +TEST_F(DatabaseFixture, Common_subRangeForeach) +{ + using namespace Database; + + struct TestCase + { + Range range; + std::size_t subRangeSize; + std::vector expectedSubRanges; + }; + + TestCase testCases[] + { + {Range{0, 0}, 1, {}}, + {Range{1, 0}, 1, {}}, + {Range{1, 1}, 1, { Range{ 1,1 } }}, + {Range{1, 3}, 1, { Range{ 1,1 }, Range {2,1}, Range{3,1} }}, + {Range{0, 100}, 100, { Range{0,100} }}, + {Range{0, 50}, 100, { Range{0,50} }}, + {Range{100, 200}, 100, { Range{100,100}, Range{200,100} }}, + {Range{100, 101}, 100, { Range{100,100}, Range{200,1}}}, + {Range{1000, 10}, 100, { Range{1000,10} }}, + {Range{1, 100}, 50, { Range{1,50}, Range{51, 50} }}, + }; + + for (const TestCase& test : testCases) + { + std::vector subRanges; + foreachSubRange(test.range, test.subRangeSize, [&](Range subRange) + { + subRanges.push_back(subRange); + return true; + }); + + EXPECT_EQ(subRanges, test.expectedSubRanges) << ", test index = " << std::distance(std::cbegin(testCases), &test); + } } TEST_F(DatabaseFixture, Common_IdType) { - using namespace Database; + using namespace Database; - { - const IdType id{}; - EXPECT_FALSE(id.isValid()); - } + { + const IdType id{}; + EXPECT_FALSE(id.isValid()); + } - { - const IdType id{0}; - EXPECT_TRUE(id.isValid()); - } + { + const IdType id{ 0 }; + EXPECT_TRUE(id.isValid()); + } - { - const IdType id1{0}; - const IdType id2{0}; - EXPECT_EQ(id1, id2); - } + { + const IdType id1{ 0 }; + const IdType id2{ 0 }; + EXPECT_EQ(id1, id2); + } - { - const IdType id1{0}; - const IdType id2{1}; - EXPECT_NE(id1, id2); - EXPECT_LT(id1, id2); - EXPECT_GT(id2, id1); - } + { + const IdType id1{ 0 }; + const IdType id2{ 1 }; + EXPECT_NE(id1, id2); + EXPECT_LT(id1, id2); + EXPECT_GT(id2, id1); + } } TEST_F(DatabaseFixture, Common_subRange) { - using namespace Database; + using namespace Database; - RangeResults results; - results.range = Range {0, 2}; - results.results = {5, 6}; - results.moreResults = false; + RangeResults results; + results.range = Range{ 0, 2 }; + results.results = { 5, 6 }; + results.moreResults = false; - { - auto subRange {results.getSubRange(Range {0, 1})}; - EXPECT_TRUE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 1); - EXPECT_EQ(subRange.results.front(), 5); - } - { - auto subRange {results.getSubRange(Range {1, 1})}; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 1); - EXPECT_EQ(subRange.results.front(), 6); - } - { - auto subRange {results.getSubRange(Range {0, 2})}; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 2); - EXPECT_EQ(subRange.results.front(), 5); - EXPECT_EQ(subRange.results.back(), 6); - } - { - auto subRange {results.getSubRange(Range {})}; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 2); - EXPECT_EQ(subRange.results.front(), 5); - EXPECT_EQ(subRange.results.back(), 6); - EXPECT_EQ(subRange.range, results.range); - } + { + auto subRange{ results.getSubRange(Range {0, 1}) }; + EXPECT_TRUE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 1); + EXPECT_EQ(subRange.results.front(), 5); + } + { + auto subRange{ results.getSubRange(Range {1, 1}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 1); + EXPECT_EQ(subRange.results.front(), 6); + } + { + auto subRange{ results.getSubRange(Range {0, 2}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 2); + EXPECT_EQ(subRange.results.front(), 5); + EXPECT_EQ(subRange.results.back(), 6); + } + { + auto subRange{ results.getSubRange(Range {}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 2); + EXPECT_EQ(subRange.results.front(), 5); + EXPECT_EQ(subRange.results.back(), 6); + EXPECT_EQ(subRange.range, results.range); + } - { - auto subRange {results.getSubRange(Range {1, 0})}; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 1); - EXPECT_EQ(subRange.results.front(), 6); - const Range expectedRange {1, 1}; - EXPECT_EQ(subRange.range, expectedRange); - } - { - auto subRange {results.getSubRange(Range {3, 2})}; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 0); - const Range expectedRange {2, 0}; - EXPECT_EQ(subRange.range, expectedRange); - } + { + auto subRange{ results.getSubRange(Range {1, 0}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 1); + EXPECT_EQ(subRange.results.front(), 6); + const Range expectedRange{ 1, 1 }; + EXPECT_EQ(subRange.range, expectedRange); + } + { + auto subRange{ results.getSubRange(Range {3, 2}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 0); + const Range expectedRange{ 2, 0 }; + EXPECT_EQ(subRange.range, expectedRange); + } }