From 2da0e98b9f39d0960dea2c8d3ea09c95c48d1a59 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 18 Jun 2026 18:41:06 +0200 Subject: [PATCH] Optimized get most recent listen query --- src/libs/database/impl/objects/Listen.cpp | 5 +- .../include/database/objects/Listen.hpp | 2 +- src/libs/database/test/Listen.cpp | 59 +++++++++++++++++-- .../scrobbling/impl/ScrobblingService.cpp | 6 +- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/libs/database/impl/objects/Listen.cpp b/src/libs/database/impl/objects/Listen.cpp index 9b13c0c6..34758c41 100644 --- a/src/libs/database/impl/objects/Listen.cpp +++ b/src/libs/database/impl/objects/Listen.cpp @@ -470,10 +470,11 @@ namespace lms::db return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT l from listen l").join("track t ON l.track_id = t.id").where("t.release_id = ?").bind(releaseId).where("l.user_id = ?").bind(userId).where("l.backend = ?").bind(backend).orderBy("l.date_time DESC").limit(1)); } - Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId) + Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, TrackId trackId) { session.checkReadTransaction(); + // TODO not pending remove? - return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT l from listen l").where("l.track_id = ?").bind(trackId).where("l.user_id = ?").bind(userId).where("l.backend = ?").bind(backend).orderBy("l.date_time DESC").limit(1)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT l from listen l").join("user u ON u.id = l.user_id").where("l.track_id = ?").bind(trackId).where("l.user_id = ?").bind(userId).where("l.backend = u.scrobbling_backend").orderBy("l.date_time DESC").limit(1)); } } // namespace lms::db diff --git a/src/libs/database/include/database/objects/Listen.hpp b/src/libs/database/include/database/objects/Listen.hpp index cf63e24c..084a3b65 100644 --- a/src/libs/database/include/database/objects/Listen.hpp +++ b/src/libs/database/include/database/objects/Listen.hpp @@ -151,7 +151,7 @@ namespace lms::db static std::size_t getCount(Session& session, UserId userId, ReleaseId trackId); // for the current backend static pointer getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId); - static pointer getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, TrackId releaseId); + static pointer getMostRecentListen(Session& session, UserId userId, TrackId trackId); // uses current scrobbling backend SyncState getSyncState() const { return _syncState; } ObjectPtr getUser() const { return _user; } diff --git a/src/libs/database/test/Listen.cpp b/src/libs/database/test/Listen.cpp index ddc27c96..b18c731c 100644 --- a/src/libs/database/test/Listen.cpp +++ b/src/libs/database/test/Listen.cpp @@ -1592,7 +1592,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; + auto listen{ Listen::getMostRecentListen(session, user->getId(), track.getId()) }; EXPECT_FALSE(listen); } @@ -1602,7 +1602,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; + auto listen{ Listen::getMostRecentListen(session, user->getId(), track.getId()) }; EXPECT_TRUE(listen); EXPECT_EQ(listen->getDateTime(), dateTime1); } @@ -1613,7 +1613,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; + auto listen{ Listen::getMostRecentListen(session, user->getId(), track.getId()) }; EXPECT_TRUE(listen); EXPECT_EQ(listen->getDateTime(), dateTime1); } @@ -1624,7 +1624,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; + auto listen{ Listen::getMostRecentListen(session, user->getId(), track.getId()) }; EXPECT_TRUE(listen); EXPECT_EQ(listen->getDateTime(), dateTime3); } @@ -1698,6 +1698,57 @@ namespace lms::db::tests } } + TEST_F(DatabaseFixture, Listen_getMostRecentTrack_byUserBackend) + { + ScopedTrack track{ session }; + ScopedUser user{ session, "MyUser" }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_FALSE(Listen::getMostRecentListen(session, user->getId(), track.getId())); + } + + const Wt::WDateTime dateTime1{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + + { + auto transaction{ session.createReadTransaction() }; + const auto listen{ Listen::getMostRecentListen(session, user->getId(), track.getId()) }; + ASSERT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime2{ Wt::WDate{ 1999, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 }; + + { + auto transaction{ session.createReadTransaction() }; + const auto listen{ Listen::getMostRecentListen(session, user->getId(), track.getId()) }; + ASSERT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime3{ Wt::WDate{ 2001, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; + ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime3 }; + + { + auto transaction{ session.createReadTransaction() }; + const auto listen{ Listen::getMostRecentListen(session, user->getId(), track.getId()) }; + ASSERT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime3); + } + + { + auto transaction{ session.createWriteTransaction() }; + user.get().modify()->setScrobblingBackend(ScrobblingBackend::ListenBrainz); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_FALSE(Listen::getMostRecentListen(session, user->getId(), track.getId())); + } + } + TEST_F(DatabaseFixture, Listen_getRecentTracks_cluster) { ScopedTrack track{ session }; diff --git a/src/libs/services/scrobbling/impl/ScrobblingService.cpp b/src/libs/services/scrobbling/impl/ScrobblingService.cpp index af528d38..b8dc5d98 100644 --- a/src/libs/services/scrobbling/impl/ScrobblingService.cpp +++ b/src/libs/services/scrobbling/impl/ScrobblingService.cpp @@ -236,14 +236,10 @@ namespace lms::scrobbling Wt::WDateTime ScrobblingService::getLastListenDateTime(db::UserId userId, db::TrackId trackId) { - const auto backend{ getUserBackend(userId) }; - if (!backend) - return {}; - Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - const db::Listen::pointer listen{ db::Listen::getMostRecentListen(session, userId, *backend, trackId) }; + const db::Listen::pointer listen{ db::Listen::getMostRecentListen(session, userId, trackId) }; return listen ? listen->getDateTime() : Wt::WDateTime{}; }