Added an option to merge artists without MBIDs to those with one, fixes #642

This commit is contained in:
emeric
2025-04-04 18:30:14 +02:00
parent 7b12dc0f70
commit faabb7ec4a
35 changed files with 1080 additions and 174 deletions
+35 -24
View File
@@ -256,30 +256,6 @@ namespace lms::db
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a").where("a.id = ?").bind(id));
}
bool Artist::exists(Session& session, ArtistId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT 1 FROM artist").where("id = ?").bind(id)) == 1;
}
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<ArtistId>(R"(SELECT DISTINCT a.id FROM artist a
WHERE NOT EXISTS (
SELECT 1
FROM track t
INNER JOIN track_artist_link t_a_l
ON t_a_l.artist_id = a.id
WHERE t.id = t_a_l.track_id
)
AND NOT EXISTS (
SELECT 1
FROM artist_info ai
WHERE ai.artist_id = a.id))") };
return utils::execRangeQuery<ArtistId>(query, range);
}
RangeResults<ArtistId> Artist::findIds(Session& session, const FindParameters& params)
{
session.checkReadTransaction();
@@ -304,6 +280,41 @@ AND NOT EXISTS (
utils::forEachQueryRangeResult(query, params.range, func);
}
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<ArtistId>(R"(SELECT DISTINCT a.id FROM artist a
WHERE NOT EXISTS (
SELECT 1
FROM track t
INNER JOIN track_artist_link t_a_l
ON t_a_l.artist_id = a.id
WHERE t.id = t_a_l.track_id
)
AND NOT EXISTS (
SELECT 1
FROM artist_info ai
WHERE ai.artist_id = a.id))") };
return utils::execRangeQuery<ArtistId>(query, range);
}
bool Artist::exists(Session& session, ArtistId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT 1 FROM artist").where("id = ?").bind(id)) == 1;
}
std::optional<core::UUID> Artist::getMBID() const
{
return core::UUID::fromString(_mbid);
}
bool Artist::hasMBID() const
{
// TODO optim this
return getMBID().has_value();
}
ObjectPtr<Image> Artist::getImage() const
{
return ObjectPtr<Image>{ _image };
+40 -1
View File
@@ -70,7 +70,7 @@ namespace lms::db
void ArtistInfo::find(Session& session, ArtistId id, const std::function<void(const pointer&)>& func)
{
find(session, id, std::nullopt, std::move(func));
find(session, id, std::nullopt, func);
}
void ArtistInfo::find(Session& session, ArtistInfoId& lastRetrievedId, std::size_t count, const std::function<void(const pointer&)>& func)
@@ -85,6 +85,45 @@ namespace lms::db
});
}
void ArtistInfo::findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const pointer&)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ArtistInfo>>("SELECT a_i FROM artist_info a_i") };
query.join("artist a ON a_i.artist_id = a.id");
query.where("a_i.mbid_matched = FALSE");
query.where("a_i.name <> a.name");
utils::applyRange(query, range);
utils::forEachQueryResult(query, [&](const pointer& info) {
func(info);
});
}
void ArtistInfo::findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const pointer&)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ArtistInfo>>("SELECT a_i FROM artist_info a_i") };
query.join("artist a ON a_i.artist_id = a.id");
query.where("a_i.mbid_matched = FALSE");
if (!allowArtistMBIDFallback)
{
query.where("a.mbid <> ''");
}
else
{
query.where(R"(
(a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid))
OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))");
}
utils::applyRange(query, range);
utils::forEachQueryResult(query, [&](const pointer& info) {
func(info);
});
}
Artist::pointer ArtistInfo::getArtist() const
{
return _artist;
+21 -1
View File
@@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
static constexpr Version LMS_DATABASE_VERSION{ 85 };
static constexpr Version LMS_DATABASE_VERSION{ 86 };
}
VersionInfo::VersionInfo()
@@ -1159,6 +1159,25 @@ FROM tracklist)");
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
void migrateFromV85(Session& session)
{
dropIndexes(session);
// Artist merging feature
utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings ADD COLUMN allow_mbid_artist_merge BOLLEAN DEFAULT(false)");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_artist_link ADD COLUMN artist_name TEXT NULL DEFAULT('')");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_artist_link ADD COLUMN artist_sort_name TEXT NULL DEFAULT('')");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_artist_link ADD COLUMN artist_mbid_matched BOOLEAN NOT NULL DEFAULT(false)");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE artist_info ADD COLUMN name TEXT NULL DEFAULT('')");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE artist_info ADD COLUMN sort_name TEXT NULL DEFAULT('')");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE artist_info ADD COLUMN mbid_matched BOOLEAN NOT NULL DEFAULT(false)");
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
bool doDbMigration(Session& session)
{
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -1220,6 +1239,7 @@ FROM tracklist)");
{ 82, migrateFromV82 },
{ 83, migrateFromV83 },
{ 84, migrateFromV84 },
{ 85, migrateFromV85 },
};
bool migrationPerformed{};
+9
View File
@@ -103,6 +103,15 @@ namespace lms::db
}
}
void ScanSettings::setAllowMBIDArtistMerge(bool value)
{
if (_allowMBIDArtistMerge != value)
{
_allowMBIDArtistMerge = value;
incScanVersion();
}
}
void ScanSettings::incScanVersion()
{
_scanVersion += 1;
+4 -2
View File
@@ -195,13 +195,14 @@ namespace lms::db
auto transaction{ createWriteTransaction() };
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_id_idx ON artist(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_image_idx ON artist(image_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_name_mbid_idx ON artist(name, mbid)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_sort_name_nocase_idx ON artist(sort_name COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_mbid_idx ON artist(mbid)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_info_path_idx ON artist_info(absolute_file_path)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_info_directory_id_idx ON artist_info(directory_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_info_artist_id_idx ON artist_info(artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS artist_info_mbid_matched_artist_idx ON artist_info(mbid_matched, artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS auth_token_user_domain_idx ON auth_token(user_id, domain)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS auth_token_domain_expiry_idx ON auth_token(domain, expiry)");
@@ -296,8 +297,9 @@ namespace lms::db
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_entry_tracklist_track_idx ON tracklist_entry(tracklist_id, track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_idx ON track_artist_link(artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_mbid_matched_artist_idx ON track_artist_link(artist_mbid_matched, artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_track_idx ON track_artist_link(artist_id, track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_type_idx ON track_artist_link(artist_id,type)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_type_idx ON track_artist_link(artist_id, type)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_track_artist_idx ON track_artist_link(track_id, artist_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_track_type_idx ON track_artist_link(track_id,type)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_type_track_artist_idx ON track_artist_link(type, track_id, artist_id)");
+71 -4
View File
@@ -55,28 +55,41 @@ namespace lms::db
}
} // namespace
TrackArtistLink::TrackArtistLink(ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type, std::string_view subType)
TrackArtistLink::TrackArtistLink(const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, std::string_view subType, bool artistMBIDMatched)
: _type{ type }
, _subType{ subType }
, _artistMBIDMatched{ artistMBIDMatched }
, _track{ getDboPtr(track) }
, _artist{ getDboPtr(artist) }
{
}
TrackArtistLink::pointer TrackArtistLink::create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type, std::string_view subType)
TrackArtistLink::pointer TrackArtistLink::create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, std::string_view subType, bool artistMBIDMatched)
{
session.checkWriteTransaction();
TrackArtistLink::pointer res{ session.getDboSession()->add(std::make_unique<TrackArtistLink>(track, artist, type, subType)) };
TrackArtistLink::pointer res{ session.getDboSession()->add(std::make_unique<TrackArtistLink>(track, artist, type, subType, artistMBIDMatched)) };
session.getDboSession()->flush();
return res;
}
std::size_t TrackArtistLink::getCount(Session& session)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_artist_link"));
}
TrackArtistLink::pointer TrackArtistLink::create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, bool artistMBIDMatched)
{
return create(session, track, artist, type, std::string_view{}, artistMBIDMatched);
}
TrackArtistLink::pointer TrackArtistLink::find(Session& session, TrackArtistLinkId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<TrackArtistLink>().where("id = ?").bind(id));
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<TrackArtistLink>>("SELECT t_a_l from track_artist_link t_a_l").where("t_a_l.id = ?").bind(id));
}
void TrackArtistLink::find(Session& session, TrackId trackId, const std::function<void(const TrackArtistLink::pointer& link, const ObjectPtr<Artist>& artist)>& func)
@@ -113,4 +126,58 @@ namespace lms::db
});
return res;
}
void TrackArtistLink::findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const TrackArtistLink::pointer&)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<TrackArtistLink>>("SELECT t_a_l FROM track_artist_link t_a_l") };
query.join("artist a ON t_a_l.artist_id = a.id");
query.where("t_a_l.artist_mbid_matched = FALSE");
query.where("t_a_l.artist_name <> a.name");
utils::applyRange(query, range);
utils::forEachQueryResult(query, [&](const TrackArtistLink::pointer& link) {
func(link);
});
}
void TrackArtistLink::findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const TrackArtistLink::pointer&)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<TrackArtistLink>>("SELECT t_a_l FROM track_artist_link t_a_l") };
query.join("artist a ON t_a_l.artist_id = a.id");
query.where("t_a_l.artist_mbid_matched = FALSE");
if (!allowArtistMBIDFallback)
{
query.where("a.mbid <> ''");
}
else
{
query.where(R"(
(a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid))
OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))");
}
utils::applyRange(query, range);
utils::forEachQueryResult(query, [&](const TrackArtistLink::pointer& link) {
func(link);
});
}
void TrackArtistLink::setArtist(ObjectPtr<Artist> artist)
{
_artist = getDboPtr(artist);
}
void TrackArtistLink::setArtistName(std::string_view artistName)
{
_artistName = artistName;
}
void TrackArtistLink::setArtistSortName(std::string_view artistSortName)
{
_artistSortName = artistSortName;
}
} // namespace lms::db