Added an option to merge artists without MBIDs to those with one, fixes #642
This commit is contained in:
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -103,6 +103,15 @@ namespace lms::db
|
||||
}
|
||||
}
|
||||
|
||||
void ScanSettings::setAllowMBIDArtistMerge(bool value)
|
||||
{
|
||||
if (_allowMBIDArtistMerge != value)
|
||||
{
|
||||
_allowMBIDArtistMerge = value;
|
||||
incScanVersion();
|
||||
}
|
||||
}
|
||||
|
||||
void ScanSettings::incScanVersion()
|
||||
{
|
||||
_scanVersion += 1;
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user