Added a dedicated table for release artists + handle display names in UI/Subsonic API, ref #731

This commit is contained in:
emeric
2025-12-24 09:00:59 +01:00
parent 68cc7116a7
commit f70692b680
78 changed files with 1916 additions and 935 deletions
+27 -1
View File
@@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
static constexpr Version LMS_DATABASE_VERSION{ 102 };
static constexpr Version LMS_DATABASE_VERSION{ 103 };
}
VersionInfo::VersionInfo()
@@ -1681,6 +1681,31 @@ FROM track)");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE podcast_episode ADD bits_per_sample INTEGER");
}
void migrateFromV102(Session& session)
{
// New link to artists from releases
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "release_artist_link" (
"id" integer primary key autoincrement,
"version" integer not null,
"artist_name" text not null,
"artist_sort_name" text not null,
"artist_mbid_matched" boolean not null,
"release_id" bigint,
"artist_id" bigint,
constraint "fk_release_artist_link_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred,
constraint "fk_release_artist_link_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred
))");
// Remove all artist links where the link type is TrackArtistLinkType::ReleaseArtist = 8
utils::executeCommand(*session.getDboSession(), R"(DELETE FROM track_artist_link WHERE type = 8)");
// Remove outdated UI state entries
utils::executeCommand(*session.getDboSession(), R"(DELETE FROM ui_state WHERE item = 'artists_link_type')");
// Just increment the scan version of the settings to make the next scan rescan all audio files
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET audio_scan_version = audio_scan_version + 1");
}
bool doDbMigration(Session& session)
{
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -1759,6 +1784,7 @@ FROM track)");
{ 99, migrateFromV99 },
{ 100, migrateFromV100 },
{ 101, migrateFromV101 },
{ 102, migrateFromV102 },
};
bool migrationPerformed{};
+2
View File
@@ -42,6 +42,7 @@
#include "database/objects/RatedRelease.hpp"
#include "database/objects/RatedTrack.hpp"
#include "database/objects/Release.hpp"
#include "database/objects/ReleaseArtistLink.hpp"
#include "database/objects/ScanSettings.hpp"
#include "database/objects/StarredArtist.hpp"
#include "database/objects/StarredRelease.hpp"
@@ -94,6 +95,7 @@ namespace lms::db
_session.mapClass<RatedRelease>("rated_release");
_session.mapClass<RatedTrack>("rated_track");
_session.mapClass<Release>("release");
_session.mapClass<ReleaseArtistLink>("release_artist_link");
_session.mapClass<ReleaseType>("release_type");
_session.mapClass<ScanSettings>("scan_settings");
_session.mapClass<StarredArtist>("starred_artist");
+13 -10
View File
@@ -27,9 +27,7 @@
#include "database/objects/Cluster.hpp"
#include "database/objects/Directory.hpp"
#include "database/objects/Release.hpp"
// #include "database/objects/StarredArtist.hpp"
#include "database/objects/Track.hpp"
// #include "database/objects/TrackArtistLink.hpp"
#include "database/objects/User.hpp"
#include "SqlQuery.hpp"
@@ -53,9 +51,8 @@ namespace lms::db
if (params.sortMethod == ArtistSortMethod::LastWrittenDesc
|| params.sortMethod == ArtistSortMethod::AddedDesc
|| params.writtenAfter.isValid()
|| params.linkType
|| params.trackArtistLinkType.has_value()
|| params.track.isValid()
|| params.release.isValid()
|| params.filters.clusters.size() == 1
|| params.filters.codec.has_value()
|| params.filters.mediaLibrary.isValid()
@@ -68,7 +65,6 @@ namespace lms::db
if (params.sortMethod == ArtistSortMethod::LastWrittenDesc
|| params.sortMethod == ArtistSortMethod::AddedDesc
|| params.writtenAfter.isValid()
|| params.release.isValid()
|| params.filters.codec.has_value()
|| params.filters.mediaLibrary.isValid()
|| params.filters.label.isValid()
@@ -79,9 +75,6 @@ namespace lms::db
if (params.writtenAfter.isValid())
query.where("t.file_last_write > ?").bind(params.writtenAfter);
if (params.release.isValid())
query.where("t.release_id = ?").bind(params.release);
if (params.filters.codec.has_value())
query.where("t.codec = ?").bind(detail::getDbCodec(*params.filters.codec));
@@ -101,8 +94,11 @@ namespace lms::db
}
}
if (params.linkType)
query.where("+t_a_l.type = ?").bind(*params.linkType); // Exclude this since the query planner does not do a good job when db is not analyzed
if (params.releaseArtistsOnly)
query.join("release_artist_link r_a_l ON r_a_l.artist_id = a.id");
if (params.trackArtistLinkType.has_value())
query.where("+t_a_l.type = ?").bind(*params.trackArtistLinkType); // Exclude this since the query planner does not do a good job when db is not analyzed
if (!params.keywords.empty())
{
@@ -318,6 +314,7 @@ namespace lms::db
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
{
// TODO extend with release artists
session.checkReadTransaction();
auto query{ session.getDboSession()->query<ArtistId>(R"(SELECT DISTINCT a.id FROM artist a
WHERE NOT EXISTS (
@@ -327,6 +324,12 @@ WHERE NOT EXISTS (
ON t_a_l.artist_id = a.id
WHERE t.id = t_a_l.track_id
)
AND NOT EXISTS (
SELECT 1
FROM release r
INNER JOIN release_artist_link r_a_l
ON r_a_l.artist_id = a.id
WHERE r.id = r_a_l.release_id)
AND NOT EXISTS (
SELECT 1
FROM artist_info ai
+16 -3
View File
@@ -42,7 +42,8 @@ namespace lms::db
for (std::string_view keyword : params.keywords)
query.where("d.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeForLikeKeyword(keyword) + "%");
if (params.artist.isValid()
if (params.trackArtist.isValid()
|| params.releaseArtist.isValid()
|| params.release.isValid()
|| params.medium.isValid())
{
@@ -62,12 +63,24 @@ namespace lms::db
if (params.release.isValid())
query.where("t.release_id = ?").bind(params.release);
if (params.artist.isValid())
if (params.releaseArtist.isValid())
{
assert(!params.trackArtist.isValid());
query.join("artist a ON a.id = r_a_l.artist_id")
.join("release_artist_link r_a_l ON r_a_l.release_id = t.release_id")
.where("a.id = ?")
.bind(params.releaseArtist);
}
if (params.trackArtist.isValid())
{
assert(!params.releaseArtist.isValid());
query.join("artist a ON a.id = t_a_l.artist_id")
.join("track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?")
.bind(params.artist);
.bind(params.trackArtist);
if (!params.trackArtistLinkTypes.empty())
{
+7 -3
View File
@@ -52,7 +52,8 @@ namespace lms::db
if (params.filters.mediaLibrary.isValid()
|| params.filters.codec.has_value()
|| params.filters.label.isValid()
|| params.filters.releaseType.isValid())
|| params.filters.releaseType.isValid()
|| params.trackArtistLinkType.has_value())
{
query.join("track t ON t.id = t_a_l.track_id");
@@ -75,8 +76,11 @@ namespace lms::db
}
}
if (params.linkType)
query.where("t_a_l.type = ?").bind(*params.linkType);
if (params.releaseArtistsOnly)
query.join("release_artist_link r_a_l ON r_a_l.artist_id = a.id");
if (params.trackArtistLinkType.has_value())
query.where("t_a_l.type = ?").bind(params.trackArtistLinkType.value());
if (!params.filters.clusters.empty())
{
+81 -34
View File
@@ -31,6 +31,7 @@
#include "database/objects/Directory.hpp"
#include "database/objects/MediaLibrary.hpp"
#include "database/objects/Medium.hpp"
#include "database/objects/ReleaseArtistLink.hpp"
#include "database/objects/Track.hpp"
#include "database/objects/TrackArtistLink.hpp"
#include "database/objects/TrackEmbeddedImage.hpp"
@@ -72,7 +73,7 @@ namespace lms::db
|| params.sortMethod == ReleaseSortMethod::OriginalDateDesc
|| params.writtenAfter.isValid()
|| params.dateRange
|| params.artist.isValid()
|| params.trackArtist.isValid()
|| params.filters.clusters.size() == 1
|| params.filters.mediaLibrary.isValid()
|| params.filters.codec.has_value()
@@ -141,13 +142,22 @@ namespace lms::db
.bind(SyncState::PendingRemove);
}
if (params.artist.isValid()
if (params.artist.isValid())
{
assert(!params.trackArtist.isValid());
query.join("release_artist_link r_a_l ON r_a_l.release_id = r.id");
query.where("r_a_l.artist_id = ?").bind(params.artist);
}
if (params.trackArtist.isValid()
|| params.sortMethod == ReleaseSortMethod::ArtistNameThenName)
{
assert(!params.artist.isValid());
query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id");
if (params.artist.isValid())
query.where("t_a_l.artist_id = ?").bind(params.artist);
if (params.trackArtist.isValid())
query.where("t_a_l.artist_id = ?").bind(params.trackArtist);
if (params.sortMethod == ReleaseSortMethod::ArtistNameThenName)
query.join("artist a ON a.id = t_a_l.artist_id");
@@ -168,30 +178,6 @@ namespace lms::db
}
query.where(oss.str());
}
if (!params.excludedTrackArtistLinkTypes.empty())
{
std::ostringstream oss;
oss << "r.id NOT IN (SELECT DISTINCT r.id FROM release r"
" INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id"
" INNER JOIN track t ON t.release_id = r.id"
" WHERE (t_a_l.artist_id = ? AND (";
query.bind(params.artist);
bool first{ true };
for (const TrackArtistLinkType linkType : params.excludedTrackArtistLinkTypes)
{
if (!first)
oss << " OR ";
oss << "t_a_l.type = ?";
query.bind(linkType);
first = false;
}
oss << ")))";
query.where(oss.str());
}
}
if (params.filters.clusters.size() == 1)
@@ -272,7 +258,7 @@ namespace lms::db
}
template<typename ResultType>
Wt::Dbo::Query<ResultType> createArtistQuery(Wt::Dbo::Session& session, std::string_view itemToSelect, ReleaseId releaseId, TrackArtistLinkType linkType)
Wt::Dbo::Query<ResultType> createTrackArtistQuery(Wt::Dbo::Session& session, std::string_view itemToSelect, ReleaseId releaseId, TrackArtistLinkType linkType)
{
auto query{ session.query<ResultType>("SELECT " + std::string{ itemToSelect } + " from artist a")
.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id")
@@ -700,19 +686,50 @@ namespace lms::db
return res;
}
std::vector<Artist::pointer> Release::getArtists(TrackArtistLinkType linkType) const
std::vector<ObjectPtr<Artist>> Release::getArtists() const
{
assert(session());
const auto query{ createArtistQuery<Wt::Dbo::ptr<Artist>>(*session(), "a", getId(), linkType) };
auto query{ session()->query<Wt::Dbo::ptr<Artist>>("SELECT a from artist a") };
query.join("release_artist_link r_a_l ON r_a_l.artist_id = a.id");
query.where("r_a_l.release_id = ?").bind(getId());
query.groupBy("a.id");
return utils::fetchQueryResults<Artist::pointer>(query);
}
std::vector<ArtistId> Release::getArtistIds(TrackArtistLinkType linkType) const
bool Release::hasArtist(ArtistId artistId) const
{
assert(session());
const auto query{ createArtistQuery<ArtistId>(*session(), "a.id", getId(), linkType) };
auto query{ session()->query<int>("SELECT COUNT(1) FROM release_artist_link r_a_l") };
query.where("r_a_l.release_id = ?").bind(getId());
query.where("r_a_l.artist_id = ?").bind(artistId);
return utils::fetchQuerySingleResult(query) > 0;
}
std::vector<Artist::pointer> Release::getTrackArtists(TrackArtistLinkType type) const
{
assert(session());
const auto query{ createTrackArtistQuery<Wt::Dbo::ptr<Artist>>(*session(), "a", getId(), type) };
return utils::fetchQueryResults<Artist::pointer>(query);
}
void Release::visitTrackArtists(TrackArtistLinkType type, std::function<void(const ObjectPtr<Artist>&)> visitor) const
{
assert(session());
const auto query{ createTrackArtistQuery<Wt::Dbo::ptr<Artist>>(*session(), "a", getId(), type) };
return utils::forEachQueryResult(query, visitor);
}
std::vector<ArtistId> Release::getTrackArtistIds(TrackArtistLinkType linkType) const
{
assert(session());
const auto query{ createTrackArtistQuery<ArtistId>(*session(), "a.id", getId(), linkType) };
return utils::fetchQueryResults(query);
}
@@ -765,6 +782,36 @@ namespace lms::db
return utils::fetchQueryResults<Medium::pointer>(query);
}
std::vector<ObjectPtr<ReleaseArtistLink>> Release::getArtistLinks() const
{
return utils::fetchQueryResults<ReleaseArtistLink::pointer>(_releaseArtistLinks.find());
}
void Release::visitArtistLinks(const std::function<void(const ReleaseArtistLink::pointer& artistLink)>& visitor) const
{
return utils::forEachQueryResult(_releaseArtistLinks.find(), visitor);
}
void Release::visitTrackArtistLinks(TrackArtistLinkType linkType, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const
{
auto query{ session()->query<Wt::Dbo::ptr<TrackArtistLink>>("SELECT t_a_l from track_artist_link t_a_l") };
query.join("track t ON t.id = t_a_l.track_id");
query.where("t.release_id = ?").bind(getId());
query.where("t_a_l.type = ?").bind(linkType);
utils::forEachQueryResult(query, visitor);
}
void Release::clearArtistLinks()
{
_releaseArtistLinks.clear();
}
void Release::addArtistLink(const ObjectPtr<ReleaseArtistLink>& artistLink)
{
_releaseArtistLinks.insert(getDboPtr(artistLink));
}
void Release::clearLabels()
{
_labels.clear();
@@ -803,7 +850,7 @@ namespace lms::db
bool Release::hasVariousArtists() const
{
// TODO optimize
return getArtists().size() > 1;
return getTrackArtists().size() > 1;
}
std::size_t Release::getTrackCount() const
@@ -0,0 +1,157 @@
/*
* Copyright (C) 2025 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/objects/ReleaseArtistLink.hpp"
#include <Wt/Dbo/Impl.h>
#include "core/ILogger.hpp"
#include "database/Session.hpp"
#include "database/objects/Artist.hpp"
#include "database/objects/Release.hpp"
#include "database/objects/Track.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
DBO_INSTANTIATE_TEMPLATES(lms::db::ReleaseArtistLink)
namespace lms::db
{
namespace
{
Wt::Dbo::Query<Wt::Dbo::ptr<ReleaseArtistLink>> createQuery(Session& session, const ReleaseArtistLink::FindParameters& params)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l FROM release_artist_link r_a_l") };
if (params.release.isValid())
query.where("r_a_l.release_id = ?").bind(params.release);
if (params.artist.isValid())
query.where("r_a_l.artist_id = ?").bind(params.artist);
if (params.sortMethod == ReleaseArtistLinkSortMethod::OriginalDateDesc)
query.join("track t ON t.release_id = r_a_l.release_id");
if (params.mbidMatched)
query.where("r_a_l.artist_mbid_matched = ?").bind(*params.mbidMatched);
switch (params.sortMethod)
{
case ReleaseArtistLinkSortMethod::None:
break;
case ReleaseArtistLinkSortMethod::OriginalDateDesc:
query.orderBy("COALESCE(t.original_date, t.date) DESC");
break;
}
return query;
}
} // namespace
ReleaseArtistLink::ReleaseArtistLink(const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched)
: _artistMBIDMatched{ artistMBIDMatched }
, _release{ getDboPtr(release) }
, _artist{ getDboPtr(artist) }
{
}
ReleaseArtistLink::pointer ReleaseArtistLink::create(Session& session, const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched)
{
session.checkWriteTransaction();
return session.getDboSession()->add(std::make_unique<ReleaseArtistLink>(release, artist, artistMBIDMatched));
}
std::size_t ReleaseArtistLink::getCount(Session& session)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_artist_link"));
}
ReleaseArtistLink::pointer ReleaseArtistLink::find(Session& session, ReleaseArtistLinkId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l from release_artist_link r_a_l").where("r_a_l.id = ?").bind(id));
}
void ReleaseArtistLink::find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func)
{
auto query{ createQuery(session, params) };
utils::forEachQueryRangeResult(query, params.range, func);
}
void ReleaseArtistLink::findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const ReleaseArtistLink::pointer&)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l from release_artist_link r_a_l") };
query.join("artist a ON r_a_l.artist_id = a.id");
query.where("r_a_l.artist_mbid_matched = FALSE");
query.where("r_a_l.artist_name <> a.name");
utils::applyRange(query, range);
utils::forEachQueryResult(query, [&](const ReleaseArtistLink::pointer& link) {
func(link);
});
}
void ReleaseArtistLink::findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const ReleaseArtistLink::pointer&)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l from release_artist_link r_a_l") };
query.join("artist a ON r_a_l.artist_id = a.id");
query.where("r_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 ReleaseArtistLink::pointer& link) {
func(link);
});
}
void ReleaseArtistLink::setArtist(ObjectPtr<Artist> artist)
{
_artist = getDboPtr(artist);
}
void ReleaseArtistLink::setArtistName(std::string_view artistName)
{
_artistName.assign(artistName, 0, Artist::maxNameLength);
LMS_LOG_IF(DB, WARNING, artistName.size() > Artist::maxNameLength, "Artist link name too long, truncated to '" << _artistName << "'");
}
void ReleaseArtistLink::setArtistSortName(std::string_view artistSortName)
{
_artistSortName.assign(artistSortName, 0, Artist::maxNameLength);
LMS_LOG_IF(DB, WARNING, artistSortName.size() > Artist::maxNameLength, "Artist link sort name too long, truncated to '" << _artistSortName << "'");
}
} // namespace lms::db
+21
View File
@@ -728,6 +728,27 @@ namespace lms::db
return utils::fetchQueryResults<TrackArtistLink::pointer>(_trackArtistLinks.find());
}
void Track::visitArtistLinks(const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const
{
utils::forEachQueryResult(_trackArtistLinks.find(), visitor);
}
std::vector<ObjectPtr<TrackArtistLink>> Track::getArtistLinks(TrackArtistLinkType type) const
{
std::vector<ObjectPtr<TrackArtistLink>> links;
visitArtistLinks(type, [&links](const ObjectPtr<TrackArtistLink>& artistLink) { links.push_back(artistLink); });
return links;
}
void Track::visitArtistLinks(TrackArtistLinkType type, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const
{
auto query{ session()->query<Wt::Dbo::ptr<TrackArtistLink>>("SELECT t_a_l from track_artist_link t_a_l") };
query.where("t_a_l.track_id = ?").bind(getId());
query.where("t_a_l.type = ?").bind(type);
return utils::forEachQueryResult(query, visitor);
}
std::vector<std::vector<Cluster::pointer>> Track::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const
{
assert(self());
@@ -88,10 +88,12 @@ namespace lms::db
{
session.checkWriteTransaction();
TrackArtistLink::pointer res{ session.getDboSession()->add(std::make_unique<TrackArtistLink>(track, artist, type, subType, artistMBIDMatched)) };
session.getDboSession()->flush();
return session.getDboSession()->add(std::make_unique<TrackArtistLink>(track, artist, type, subType, artistMBIDMatched));
}
return res;
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);
}
std::size_t TrackArtistLink::getCount(Session& session)
@@ -101,11 +103,6 @@ namespace lms::db
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();
@@ -125,10 +122,10 @@ namespace lms::db
});
}
void TrackArtistLink::find(Session& session, const FindParameters& parameters, const std::function<void(const TrackArtistLink::pointer&)>& func)
void TrackArtistLink::find(Session& session, const FindParameters& params, const std::function<void(const TrackArtistLink::pointer&)>& func)
{
auto query{ createQuery(session, parameters) };
utils::forEachQueryRangeResult(query, parameters.range, func);
auto query{ createQuery(session, params) };
utils::forEachQueryRangeResult(query, params.range, func);
}
core::EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId)
-2
View File
@@ -43,8 +43,6 @@ namespace lms::db
return "performer";
case TrackArtistLinkType::Producer:
return "producer";
case TrackArtistLinkType::ReleaseArtist:
return "albumartist";
case TrackArtistLinkType::Remixer:
return "remixer";
case TrackArtistLinkType::Writer: